diff --git a/.env.dist b/.env.dist index 0e6a0174..b2246030 100644 --- a/.env.dist +++ b/.env.dist @@ -1,3 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + GITHUB_USER=github: put here your github user MY_OPERATOR_IMAGE=ghcr.io/${GITHUB_USER}/openserverless-operator diff --git a/.github/cisetup.sh b/.github/cisetup.sh index 80a073b2..1a1bdd25 100644 --- a/.github/cisetup.sh +++ b/.github/cisetup.sh @@ -31,8 +31,6 @@ curl -sL $URL | tar tzvf - -C ~/.local/bin YQ_VER=v4.27.2 YQ_BIN=yq_linux_amd64 sudo wget https://github.com/mikefarah/yq/releases/download/${YQ_VER}/${YQ_BIN} -O /usr/bin/yq && sudo chmod +x /usr/bin/yq -MC_VER=RELEASE.2025-05-21T01-59-54Z -sudo wget https://dl.min.io/client/mc/release/linux-${ARCH}/archive/mc.${MC_VER} -O /usr/bin/mc && sudo chmod +x /usr/bin/mc #URL="https://dl.k8s.io/release/$VER/bin/linux/$ARCH/kubectl" #curl -sSL "$URL" | sudo tee /usr/local/bin/kubectl && sudo chmod +x /usr/bin/kubectl #kubectl version diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index feea6e0a..515e3c6d 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -51,7 +51,7 @@ jobs: echo "MY_OPERATOR_IMAGE=$IMAGE" >> .env cat .env - name: Kind - uses: container-tools/kind-action@v2.0.4 + uses: helm/kind-action@06c1ae10762d3b9c1644e7fe69596ae519e015a2 # v1.15.0 with: version: v0.22.0 cluster_name: openserverless @@ -64,13 +64,49 @@ jobs: run: bash .github/cisetup.sh - name: Install run: task setup + - name: Regression tests + run: poetry run python3 -m unittest discover -s tests -p 'test_*.py' -v - name: BuildAndLoad run: task build-and-load - name: AddIngress - run: task kind:ingress + run: | + task kind:ingress + # Kind ingress uses hostPort 80/443. Release its unused NodePorts so + # dynamic allocation cannot occupy fixed test ports such as 30984. + kubectl -n ingress-nginx patch service ingress-nginx-controller --type=strategic \ + -p '{"spec":{"type":"ClusterIP","allocateLoadBalancerNodePorts":null,"ports":[{"port":80,"nodePort":null},{"port":443,"nodePort":null}]}}' + test -z "$(kubectl -n ingress-nginx get service ingress-nginx-controller -o jsonpath='{.spec.ports[*].nodePort}')" + kubectl get services --all-namespaces - name: Unit Test run: task utest continue-on-error: false - name: Integration Test run: task itest continue-on-error: false + - name: Reject new integration failures + run: | + # The legacy runner does not propagate failure. Only the exact original + # 0.9.0 test scripts (3ee0a79; reproduced in run 38893750250) may retain + # their known warning. An updated test must pass, so both exceptions + # automatically disappear once the two independent fixes are merged. + unexpected_failures=0 + if [ -f _failed.txt ]; then + while IFS= read -r failed_test; do + case "$failed_test" in + tests/kind/ferretdb_test.ipy) + baseline_blob=90e70ffe1ae1b126e5bd651f32a6a6240b4def07 + ;; + tests/kind/runtimes_preloader_test.ipy) + baseline_blob=afbee5e8f49945f4737c507de36c731e1da6faee + ;; + *) baseline_blob= ;; + esac + if [ -n "$baseline_blob" ] && [ "$(git hash-object -- "$failed_test")" = "$baseline_blob" ]; then + echo "::warning::Pre-existing 0.9.0 integration failure in unchanged baseline test: $failed_test" + else + echo "::error::Integration regression: $failed_test" + unexpected_failures=1 + fi + done < _failed.txt + fi + exit "$unexpected_failures" diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index b5ba46e9..a171990e 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -23,7 +23,7 @@ on: tags: - '*.*.*.*' branches-ignore: - - '*' + - '**' workflow_dispatch: jobs: @@ -59,9 +59,11 @@ jobs: git config user.email "41898282+github-actions[bot]@users.noreply.github.com" task tag task ci - - name: Assign TAG from push - run: | echo "IMG_TAG=$(git describe --tags --abbrev=0)" >> "$GITHUB_ENV" + - name: Assign TAG from push + if: ${{ github.event_name == 'push' }} + run: echo "IMG_TAG=${{ github.ref_name }}" >> "$GITHUB_ENV" + - name: Generate .env run: | OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') @@ -80,7 +82,7 @@ jobs: - name: Setup run: bash .github/cisetup.sh - name: Kind - uses: container-tools/kind-action@v2.0.4 + uses: helm/kind-action@06c1ae10762d3b9c1644e7fe69596ae519e015a2 # v1.15.0 with: version: v0.22.0 cluster_name: openserverless @@ -121,7 +123,7 @@ jobs: echo "REGISTRY_TOKEN=${{ secrets.DOCKERHUB_TOKEN }}" >> "$GITHUB_ENV" fi - name: Registry login - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ${{ env.REGISTRY }} username: ${{ env.REGISTRY_USER }} diff --git a/.gitignore b/.gitignore index ce425308..557d8539 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. # +.DS_Store .git-hooks/ __pycache__/ __pypackages__ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29b..00000000 diff --git a/.licenserc.yaml b/.licenserc.yaml index 4cabfb12..db44bbdb 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -21,48 +21,17 @@ header: copyright-owner: Apache Software Foundation paths-ignore: - - '**/.git-hooks/*' - - 'playground' - - '**/*.off' - - '**/*.excalidraw' - - '.pdm.toml' - - 'nodesource_setup.sh' - - '**/pdm.lock' - '**/LICENSE' - '**/DISCLAIMER' - '**/NOTICE' - '**/WARN' - - '**/package-lock.json' - - '**/.*-version' - - '**/*.json' - - '**/*.txt' - '**/node_modules/**' - '**/__pypackages__/**' - - '**/build/**' - - '**/target/**' - - '**/openwhisk/**' - - '**/openwhisk-*/**' - - '**/parthenon/**' - - '**/*.kubeconfig' - - '**/license-eye' - - '**/id_rsa*' - - '**/.gitkeep' - - '.gitmodules' - - 'poetry.lock' - - '**/__*' - - '.github/ngrok' - - 'ngrok' - - 'ngrok.log' - - 'poetry.lock' - - 'installer/**' - - 'nuv/**' - - 'olaris/**' - - 'skip-test' - - '.env.dist' - - '**/ow_pod_templates.yaml' + - '**/*.json' + - '**/*.txt' dependency: files: - go.mod - - package.json + - package.json diff --git a/Dockerfile b/Dockerfile index 6b97cbeb..c0b1b501 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,6 @@ WORKDIR /home/openserverless ADD --chown=openserverless:openserverless openserverless/*.py /home/openserverless/openserverless/ ADD --chown=openserverless:openserverless openserverless/files /home/openserverless/openserverless/files ADD --chown=openserverless:openserverless openserverless/templates /home/openserverless/openserverless/templates -ADD --chown=openserverless:openserverless openserverless/policies /home/openserverless/openserverless/policies ADD --chown=openserverless:openserverless deploy/openserverless-operator /home/openserverless/deploy/openserverless-operator ADD --chown=openserverless:openserverless deploy/openserverless-permissions /home/openserverless/deploy/openserverless-permissions ADD --chown=openserverless:openserverless deploy/openwhisk-standalone /home/openserverless/deploy/openwhisk-standalone @@ -36,13 +35,9 @@ ADD --chown=openserverless:openserverless deploy/openwhisk-endpoint /home/opense ADD --chown=openserverless:openserverless deploy/couchdb /home/openserverless/deploy/couchdb ADD --chown=openserverless:openserverless deploy/redis /home/openserverless/deploy/redis ADD --chown=openserverless:openserverless deploy/scheduler /home/openserverless/deploy/scheduler -ADD --chown=openserverless:openserverless deploy/mongodb-operator /home/openserverless/deploy/mongodb-operator -ADD --chown=openserverless:openserverless deploy/mongodb-operator-deploy /home/openserverless/deploy/mongodb-operator-deploy -ADD --chown=openserverless:openserverless deploy/mongodb-standalone /home/openserverless/deploy/mongodb-standalone ADD --chown=openserverless:openserverless deploy/cert-manager /home/openserverless/deploy/cert-manager ADD --chown=openserverless:openserverless deploy/ingress-nginx /home/openserverless/deploy/ingress-nginx ADD --chown=openserverless:openserverless deploy/issuer /home/openserverless/deploy/issuer -ADD --chown=openserverless:openserverless deploy/minio /home/openserverless/deploy/minio ADD --chown=openserverless:openserverless deploy/kafka /home/openserverless/deploy/kafka ADD --chown=openserverless:openserverless deploy/zookeeper /home/openserverless/deploy/zookeeper ADD --chown=openserverless:openserverless deploy/nginx-static /home/openserverless/deploy/nginx-static @@ -55,7 +50,10 @@ ADD --chown=openserverless:openserverless deploy/postgres-backup /home/openserve ADD --chown=openserverless:openserverless run.sh dbinit.sh cron.sh pyproject.toml poetry.lock whisk-system.sh /home/openserverless/ # prepares the required folders to deploy the whisk-system actions -RUN mkdir /home/openserverless/deploy/whisk-system +USER root +RUN mkdir -p /home/openserverless/deploy/whisk-system && \ + chown openserverless:openserverless /home/openserverless/deploy/whisk-system +USER openserverless ADD --chown=openserverless:openserverless actions /home/openserverless/actions # enterprise specific @@ -77,6 +75,9 @@ ADD --chown=openserverless:openserverless quota.sh /home/openserverless/ # Python dependencies FROM python:3.12-slim-bookworm AS deps +RUN groupadd --gid 1001 openserverless && \ + useradd -m openserverless -s /bin/bash --uid 1001 --gid 1001 --groups root + # --- Install Poetry --- ARG POETRY_VERSION=2.3.2 ENV POETRY_HOME=/opt/poetry @@ -156,4 +157,4 @@ RUN mkdir -p /home/openserverless/deploy/whisk-system && \ # Apache release metadata (see DISCLAIMER, LICENSE, NOTICE) COPY DISCLAIMER LICENSE NOTICE / -CMD ["./run.sh"] \ No newline at end of file +CMD ["./run.sh"] diff --git a/LICENSE b/LICENSE index 1bffe5eb..b85e6eb5 100644 --- a/LICENSE +++ b/LICENSE @@ -201,252 +201,262 @@ See the License for the specific language governing permissions and limitations under the License. +--------------- -======================================================================== -Apache License 2.0 -======================================================================== - -This distribution bundles the following components, which are available under an Apache License 2.0 (https://opensource.org/licenses/Apache-2.0). -Spray Caching 1.3.4 (io.spray:spray-caching_2.11:1.3.4 - http://spray.io/documentation/1.2.4/spray-caching/) - ConcurrentMapBackedCache.scala under common/scala/src/main/scala/org/apache/openwhisk/core/database/ contains implementation - from Spray's [[spray.caching.Cache]] and [[spray.caching.SimpleLruCache]] respectively. - License included at licenses/LICENSE-spray.txt, or https://github.com/spray/spray/blob/master/LICENSE - Copyright (C) 2011-2015 the spray project - -This product bundles the files gradlew and gradlew.bat from Gradle v5.5 -which are distributed under the Apache License, Version 2.0. -For details see ./gradlew and ./gradlew.bat. - - -============================================================================== -BEGIN BUNDLED THIRD-PARTY SOFTWARE INVENTORY (container image) -============================================================================== +The Apache release artifact for Apache OpenServerless is the SOURCE +distribution, which is licensed in its entirety under the Apache License, +Version 2.0, reproduced above. The container image built from the +Dockerfile beside this file is a CONVENIENCE BINARY which additionally +bundles the third-party components recorded below, each under its own +license. -============================================================================== -LICENSES OF BUNDLED THIRD-PARTY SOFTWARE -Container image: apache/openserverless-operator -============================================================================== +The ASF classification of these licenses follows +. Components whose licenses +are not compatible with the Apache License 2.0, and components that +require special handling, are enumerated in the WARN file. -Apache OpenServerless itself is licensed under the Apache License, -Version 2.0, reproduced above. The container image built from the -Dockerfile beside this file additionally bundles the third-party -components listed below, each under its own license. - -Refer to https://www.apache.org/legal/resolved.html for the ASF -classification of these licenses. Components that are NOT compatible -with the Apache License 2.0 are enumerated in the WARN file. - ------------------------------------------------------------------------------- -Summary by license ------------------------------------------------------------------------------- - -Apache-2.0 - OpenWhisk CLI (wsk) (1.2.0) - aiosignal (1.4.0) - asttokens (3.0.0) - bcrypt (4.2.1) - frozenlist (1.5.0) - google-auth (2.37.0) - kubectl (v1.23.0) - kubernetes (31.0.0) - kustomize (v4.5.7) - minio (7.2.14) - multidict (6.1.0) - propcache (0.2.1) - pykube-ng (23.6.0) - requests (2.32.4) - rsa (4.9) - tzdata (2024.2) - websocket-client (1.8.0) - yarl (1.18.3) - -Apache-2.0 AND MIT - aiohttp (3.14.3) - -Apache-2.0 OR BSD-3-Clause - python-dateutil (2.9.0.post0) - -BSD-2-Clause - decorator (5.1.1) - pyasn1 (0.6.4) - pyasn1-modules (0.4.2) - pygments (2.19.1) - python-json-logger (3.2.1) - -BSD-2-Clause AND Public Domain - pycryptodome (3.21.0) - -BSD-3-Clause - click (8.1.8) - colorama (0.4.6) - flatdict (4.1.0) - idna (3.10) - ipython (8.31.0) - jinja2 (3.1.5) - markupsafe (3.0.2) - matplotlib-inline (0.1.7) - oauthlib (3.2.2) - pg8000 (1.31.5) - prompt-toolkit (3.0.48) - pycparser (2.22) - traitlets (5.14.3) - -ISC - pexpect (4.9.0) - ptyprocess (0.7.0) - requests-oauthlib (2.0.0) - -MIT-0 - scramp (1.4.17) - -MIT - Task (taskfile) (latest) - argon2-cffi (23.1.0) - asn1crypto (1.5.1) - argon2-cffi-bindings (21.2.0) - attrs (24.3.0) - backoff (2.2.1) - cachetools (5.5.0) - cffi (1.17.1) - charset-normalizer (3.4.1) - croniter (1.4.1) - durationpy (0.9) - executing (2.1.0) - iso8601 (2.1.0) - jedi (0.19.2) - kopf (1.37.4) - parso (0.8.4) - poetry (2.3.2) - pure-eval (0.2.3) - pyyaml (6.0.2) - redis (5.2.1) - six (1.17.0) - stack-data (0.6.3) - urllib3 (2.3.0) - wcwidth (0.2.13) - -MPL-2.0 - certifi (2024.12.14) - -PSF-2.0 - aiohappyeyeballs (2.7.1) - typing-extensions (4.12.2) - -curl: MIT; zip/unzip: Info-ZIP - curl, zip, unzip ((Debian bullseye)) - -mixed; see WARN - (see the FROM line of the Dockerfile) (-) - ------------------------------------------------------------------------------- -Detail by component ------------------------------------------------------------------------------- - -Python runtime dependencies (pyproject.toml / poetry.lock) ----------------------------------------------------------- - aiohappyeyeballs (2.7.1) -- PSF-2.0 - aiohttp (3.14.3) -- Apache-2.0 AND MIT - aiosignal (1.4.0) -- Apache-2.0 - argon2-cffi (23.1.0) -- MIT - argon2-cffi-bindings (21.2.0) -- MIT - asn1crypto (1.5.1) -- MIT - asttokens (3.0.0) -- Apache-2.0 - attrs (24.3.0) -- MIT - backoff (2.2.1) -- MIT - bcrypt (4.2.1) -- Apache-2.0 - cachetools (5.5.0) -- MIT - certifi (2024.12.14) -- MPL-2.0 - Category B: used unmodified, as a separate file. - cffi (1.17.1) -- MIT - charset-normalizer (3.4.1) -- MIT - click (8.1.8) -- BSD-3-Clause - colorama (0.4.6) -- BSD-3-Clause - croniter (1.4.1) -- MIT - decorator (5.1.1) -- BSD-2-Clause - durationpy (0.9) -- MIT - executing (2.1.0) -- MIT - flatdict (4.1.0) -- BSD-3-Clause - frozenlist (1.5.0) -- Apache-2.0 - google-auth (2.37.0) -- Apache-2.0 - idna (3.10) -- BSD-3-Clause - ipython (8.31.0) -- BSD-3-Clause - Dev-only dependency. - iso8601 (2.1.0) -- MIT - jedi (0.19.2) -- MIT - jinja2 (3.1.5) -- BSD-3-Clause - kopf (1.37.4) -- MIT - kubernetes (31.0.0) -- Apache-2.0 - markupsafe (3.0.2) -- BSD-3-Clause - matplotlib-inline (0.1.7) -- BSD-3-Clause - minio (7.2.14) -- Apache-2.0 - multidict (6.1.0) -- Apache-2.0 - oauthlib (3.2.2) -- BSD-3-Clause - parso (0.8.4) -- MIT - pexpect (4.9.0) -- ISC - pg8000 (1.31.5) -- BSD-3-Clause - prompt-toolkit (3.0.48) -- BSD-3-Clause - propcache (0.2.1) -- Apache-2.0 - ptyprocess (0.7.0) -- ISC - pure-eval (0.2.3) -- MIT - pyasn1 (0.6.4) -- BSD-2-Clause - pyasn1-modules (0.4.2) -- BSD-2-Clause - pycparser (2.22) -- BSD-3-Clause - pycryptodome (3.21.0) -- BSD-2-Clause AND Public Domain - pygments (2.19.1) -- BSD-2-Clause - pykube-ng (23.6.0) -- Apache-2.0 - python-dateutil (2.9.0.post0) -- Apache-2.0 OR BSD-3-Clause - python-json-logger (3.2.1) -- BSD-2-Clause - pyyaml (6.0.2) -- MIT - redis (5.2.1) -- MIT - requests (2.32.4) -- Apache-2.0 - requests-oauthlib (2.0.0) -- ISC - rsa (4.9) -- Apache-2.0 - scramp (1.4.17) -- MIT-0 - six (1.17.0) -- MIT - stack-data (0.6.3) -- MIT - traitlets (5.14.3) -- BSD-3-Clause - typing-extensions (4.12.2) -- PSF-2.0 - tzdata (2024.2) -- Apache-2.0 - urllib3 (2.3.0) -- MIT - wcwidth (0.2.13) -- MIT - websocket-client (1.8.0) -- Apache-2.0 - yarl (1.18.3) -- Apache-2.0 - -Build-time and bundled command-line tools ------------------------------------------ - poetry (2.3.2) -- MIT - Build-time dependency manager. - kubectl (v1.23.0) -- Apache-2.0 - Downloaded from dl.k8s.io at build time. - kustomize (v4.5.7) -- Apache-2.0 - kubernetes-sigs/kustomize. - OpenWhisk CLI (wsk) (1.2.0) -- Apache-2.0 - Apache OpenWhisk. - Task (taskfile) (latest) -- MIT - Installed from taskfile.dev at build time. - curl, zip, unzip ((Debian bullseye)) -- curl: MIT; zip/unzip: Info-ZIP - -Base operating system image ---------------------------- - (see the FROM line of the Dockerfile) (-) -- mixed; see WARN - Operating-system components are supplied by the upstream base image and are not - redistributed by the ASF in source form. - ------------------------------------------------------------------------------- -Full license texts ------------------------------------------------------------------------------- - -The complete text of the Apache License 2.0 is reproduced at the top of -this file. The complete texts of the other licenses referenced above are -distributed with each component inside the container image, in the -package metadata directories of the respective package manager: +The complete texts of the licenses referenced below, other than those +reproduced in this file, are distributed with each component inside the +container image, in the package metadata directories of the respective +package manager: * Python packages: /.dist-info/ (LICENSE, METADATA) - * Node.js packages: /node_modules//LICENSE - * PHP packages: /phpAction/composer/vendor//LICENSE - * Go modules: vendored module sources / pkg.go.dev - * Debian packages: /usr/share/doc//copyright - -============================================================================== -END BUNDLED THIRD-PARTY SOFTWARE INVENTORY -============================================================================== + * Debian packages: /usr/share/doc//copyright + +--------------- + +openserverless-operator contains ConcurrentMapBackedCache.scala in +`common/scala/src/main/scala/org/apache/openwhisk/core/database/`, which contains +an implementation derived from Spray Caching 1.3.4 +, namely from +`spray.caching.Cache` and `spray.caching.SimpleLruCache`. This code was +released under an Apache 2.0 license. +Copyright (C) 2011-2015 the spray project + +--------------- + +The openserverless-operator container image bundles the following Python +runtime dependencies, resolved from pyproject.toml and poetry.lock, which +were released under an Apache 2.0 license. + + * aiosignal (1.4.0) + * asttokens (3.0.0) + * bcrypt (4.2.1) + * frozenlist (1.5.0) + * google-auth (2.37.0) + * kubernetes (31.0.0) + * minio (7.2.14) + * multidict (6.1.0) + * propcache (0.2.1) + * pykube-ng (23.6.0) + * requests (2.32.4) + * rsa (4.9) + * tzdata (2024.2) + * websocket-client (1.8.0) + * yarl (1.18.3) + +aiohttp (3.14.3) was released under an Apache 2.0 AND MIT license. +python-dateutil (2.9.0.post0) was released under an Apache 2.0 OR +BSD-3-Clause license. + +--------------- + +The openserverless-operator container image bundles the following build-time +and command-line tools, which were released under an Apache 2.0 license. + + * kubectl (v1.23.0), downloaded from dl.k8s.io at build time + * kustomize (v4.5.7), kubernetes-sigs/kustomize + * OpenWhisk CLI (wsk) (1.2.0), Apache OpenWhisk + +--------------- + +The openserverless-operator container image bundles the following components +which were released under an MIT license. + + * argon2-cffi (23.1.0) + * argon2-cffi-bindings (21.2.0) + * asn1crypto (1.5.1) + * attrs (24.3.0) + * backoff (2.2.1) + * cachetools (5.5.0) + * cffi (1.17.1) + * charset-normalizer (3.4.1) + * croniter (1.4.1) + * durationpy (0.9) + * executing (2.1.0) + * iso8601 (2.1.0) + * jedi (0.19.2) + * kopf (1.37.4) + * parso (0.8.4) + * poetry (2.3.2), build-time dependency manager + * pure-eval (0.2.3) + * pyyaml (6.0.2) + * redis (5.2.1) + * six (1.17.0) + * stack-data (0.6.3) + * Task (taskfile), installed from taskfile.dev at build time + * urllib3 (2.3.0) + * wcwidth (0.2.13) + +curl ((Debian bullseye)) was released under the curl License, an MIT +license. scramp (1.4.17) was released under an MIT-0 license. + +The MIT license reads as follows: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--------------- + +The openserverless-operator container image bundles the following components +which were released under a BSD-3-Clause license. + + * click (8.1.8) + * colorama (0.4.6) + * flatdict (4.1.0) + * idna (3.10) + * ipython (8.31.0), dev-only dependency + * jinja2 (3.1.5) + * markupsafe (3.0.2) + * matplotlib-inline (0.1.7) + * oauthlib (3.2.2) + * pg8000 (1.31.5) + * prompt-toolkit (3.0.48) + * pycparser (2.22) + * traitlets (5.14.3) + +The BSD-3-Clause license reads as follows: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------- + +The openserverless-operator container image bundles the following components +which were released under a BSD-2-Clause license. + + * decorator (5.1.1) + * pyasn1 (0.6.4) + * pyasn1-modules (0.4.2) + * pygments (2.19.1) + * python-json-logger (3.2.1) + +pycryptodome (3.21.0) was released under a BSD-2-Clause AND Public Domain +license. + +The BSD-2-Clause license reads as follows: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------- + +The openserverless-operator container image bundles the following components +which were released under an ISC license. + + * pexpect (4.9.0) + * ptyprocess (0.7.0) + * requests-oauthlib (2.0.0) + +The ISC license reads as follows: + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +--------------- + +The openserverless-operator container image bundles aiohappyeyeballs (2.7.1) +and typing-extensions (4.12.2), which were released under a PSF-2.0 license. +The complete text of the Python Software Foundation License Version 2 is +available at and is distributed with +each component inside the container image. + +--------------- + +The openserverless-operator container image bundles zip and unzip +((Debian bullseye)), which were released under the Info-ZIP license. The +complete text of the Info-ZIP license is available at + and is distributed with each +component inside the container image. + +--------------- + +The openserverless-operator container image bundles certifi (2024.12.14), +which was released under an MPL-2.0 license. It is a Category B component: +it is included in binary form only, unmodified and as a separate file. The +complete text of the Mozilla Public License, Version 2.0 is available at + and is distributed with the component inside +the container image. See the WARN file. + +--------------- + +The openserverless-operator container image is built on an upstream base +operating system image, named on the FROM line of the Dockerfile, whose +userland is licensed under mixed licenses including GPL-2.0 and GPL-3.0. +Those operating-system components are supplied by the upstream base image +and are not redistributed by the ASF in source form. See the WARN file. + diff --git a/MESSAGING_SECURITY.md b/MESSAGING_SECURITY.md new file mode 100644 index 00000000..73f060aa --- /dev/null +++ b/MESSAGING_SECURITY.md @@ -0,0 +1,94 @@ + + +# Kafka and ZooKeeper tenant isolation + +The full deployment runs untrusted action pods alongside the shared Kafka and +ZooKeeper services. Those services are private control-plane endpoints; tenant +actions must not connect to them, even when the action knows their DNS names or +pod IPs. + +The operator installs ingress NetworkPolicies before reconciling either +component. Kafka's broker port accepts only controller, invoker, scheduler and +Kafka pods in the same namespace. Kafka metrics accept only the installation's +Prometheus pods. ZooKeeper's client port accepts Kafka, invoker and ZooKeeper +pods; quorum ports accept only ZooKeeper peers. Every allowed peer selector +also excludes pods carrying the `user-action-pod` label, regardless of its value. +The invoker controls action pod labels; action annotations cannot grant these +network permissions. + +The policies are included in rendered and saved component specs and inherit the +Whisk owner reference. Policy application failure stops workload reconciliation. +A pod-template revision rolls legacy Kafka and ZooKeeper pods once, closing +connections established before the boundary was installed. Reconciliation waits +for the rollout. When called with a saved component spec, the explicit deletion +helper keeps the policy until all component pods have disappeared; a deletion +timeout retains the policy. + +Normal owner-backed Whisk deletion currently relies on Kubernetes owner garbage +collection, which does not guarantee that the policy outlives the messaging pods. +The ordered helper is not reached when the process-local saved spec is absent. +This teardown race is a known, accepted limitation of this change; policy-last +deletion is not guaranteed during Whisk uninstall. Normal operation and automatic +upgrade isolation are covered by the verification below. + +## Deployment requirements + +The cluster must enforce Kubernetes NetworkPolicy. The presence of a policy API +object alone is not proof of enforcement. K3s with its network-policy controller +enabled is supported by this boundary; an installation with +`--disable-network-policy`, or a CNI without policy support, needs an enforcing +network provider before this protection is effective. Do not claim tenant +isolation on such a cluster without a live denied-connection test. + +For an existing full installation, the patched operator automatically reconciles +enabled ZooKeeper and Kafka components when it resumes or processes a Whisk +configuration update. The supported `ops update operator` flow therefore installs +the boundary without manual internal calls. Reconciliation applies ZooKeeper +before Kafka, propagates errors to the operator handler, and waits for each +StatefulSet rollout before continuing normal post-resume or update processing. +A missing Whisk owner aborts reconciliation. Slim installations do not deploy +messaging components. Repeating a restart preserves existing policies and does +not roll unchanged pod templates. Plan for the single-replica service restart on +the first upgrade that installs this boundary. Do not remove the policies to work +around an unrelated connectivity failure. Kubernetes NetworkPolicies are +additive: an administrator-installed broad allow policy can reopen access. + +This patch preserves broker protocols, component versions and PVC contents. It +enforces the ordinary tenant-action boundary at the network layer; it does not +add TLS, Kafka SASL/ACLs or ZooKeeper authentication. Host/node administrators, +Kubernetes administrators and compromised allowed control-plane pods remain +trusted. Service-level credentials would provide additional protection against +those separate paths. Slim deployments do not create these messaging components. + +## Verification + +Run `python -m unittest discover -s tests -p test_messaging_security.py` in the +operator environment. Live acceptance must additionally demonstrate: + +- A normal action in each of two tenants cannot reach Kafka or ZooKeeper by + Service name, pod DNS name or direct pod IP, or Kafka metrics ports. +- A pod in another namespace cannot gain access by copying trusted labels. +- Action-labelled pods remain denied even with a trusted component label. +- Controller/invoker Kafka traffic, invoker ZooKeeper traffic, broker-to- + ZooKeeper traffic and Prometheus metric scraping still work. +- Invoker restart preserves its assigned ID and successful tenant invocations. +- Updating an existing installation through `ops update operator` installs both + policies and rolls legacy messaging pods without manual reconciliation; + repeating the operator restart preserves the messaging pod identities. +- Alpha/Beta application, Redis Lua, Streamer and scheduled actions still work + without editing their code. diff --git a/NOTICE b/NOTICE index 7f6547bc..e9f9593f 100644 --- a/NOTICE +++ b/NOTICE @@ -1,196 +1,81 @@ Apache OpenServerless (Incubating) -Copyright 2024 The Apache Software Foundation +Copyright 2024-2026 The Apache Software Foundation This product includes software developed at -The Apache Software Foundation (http://www.apache.org/). - - -============================================================================== -BEGIN BUNDLED THIRD-PARTY SOFTWARE INVENTORY (container image) -============================================================================== - ------------------------------------------------------------------------------- -Container image: apache/openserverless-operator ------------------------------------------------------------------------------- - -This image bundles third-party software. The components below are -redistributed under the licenses recorded in the LICENSE file that -accompanies this NOTICE. Components flagged '*** SEE WARN ***' are -listed in the WARN file as incompatible with the Apache License 2.0. - -Python runtime dependencies (pyproject.toml / poetry.lock) ----------------------------------------------------------- - * aiohappyeyeballs (2.7.1) - License: PSF-2.0 - * aiohttp (3.14.3) - License: Apache-2.0 AND MIT - * aiosignal (1.4.0) - License: Apache-2.0 - * argon2-cffi (23.1.0) - License: MIT - * argon2-cffi-bindings (21.2.0) - License: MIT - * asn1crypto (1.5.1) - License: MIT - * asttokens (3.0.0) - License: Apache-2.0 - * attrs (24.3.0) - License: MIT - * backoff (2.2.1) - License: MIT - * bcrypt (4.2.1) - License: Apache-2.0 - * cachetools (5.5.0) - License: MIT - * certifi (2024.12.14) - License: MPL-2.0 - Category B: used unmodified, as a separate file. - * cffi (1.17.1) - License: MIT - * charset-normalizer (3.4.1) - License: MIT - * click (8.1.8) - License: BSD-3-Clause - * colorama (0.4.6) - License: BSD-3-Clause - * croniter (1.4.1) - License: MIT - * decorator (5.1.1) - License: BSD-2-Clause - * durationpy (0.9) - License: MIT - * executing (2.1.0) - License: MIT - * flatdict (4.1.0) - License: BSD-3-Clause - * frozenlist (1.5.0) - License: Apache-2.0 - * google-auth (2.37.0) - License: Apache-2.0 - * idna (3.10) - License: BSD-3-Clause - * ipython (8.31.0) - License: BSD-3-Clause - Dev-only dependency. - * iso8601 (2.1.0) - License: MIT - * jedi (0.19.2) - License: MIT - * jinja2 (3.1.5) - License: BSD-3-Clause - * kopf (1.37.4) - License: MIT - * kubernetes (31.0.0) - License: Apache-2.0 - * markupsafe (3.0.2) - License: BSD-3-Clause - * matplotlib-inline (0.1.7) - License: BSD-3-Clause - * minio (7.2.14) - License: Apache-2.0 - * multidict (6.1.0) - License: Apache-2.0 - * oauthlib (3.2.2) - License: BSD-3-Clause - * parso (0.8.4) - License: MIT - * pexpect (4.9.0) - License: ISC - * pg8000 (1.31.5) - License: BSD-3-Clause - * prompt-toolkit (3.0.48) - License: BSD-3-Clause - * propcache (0.2.1) - License: Apache-2.0 - * ptyprocess (0.7.0) - License: ISC - * pure-eval (0.2.3) - License: MIT - * pyasn1 (0.6.4) - License: BSD-2-Clause - * pyasn1-modules (0.4.2) - License: BSD-2-Clause - * pycparser (2.22) - License: BSD-3-Clause - * pycryptodome (3.21.0) - License: BSD-2-Clause AND Public Domain - * pygments (2.19.1) - License: BSD-2-Clause - * pykube-ng (23.6.0) - License: Apache-2.0 - * python-dateutil (2.9.0.post0) - License: Apache-2.0 OR BSD-3-Clause - * python-json-logger (3.2.1) - License: BSD-2-Clause - * pyyaml (6.0.2) - License: MIT - * redis (5.2.1) - License: MIT - * requests (2.32.4) - License: Apache-2.0 - * requests-oauthlib (2.0.0) - License: ISC - * rsa (4.9) - License: Apache-2.0 - * scramp (1.4.17) - License: MIT-0 - * six (1.17.0) - License: MIT - * stack-data (0.6.3) - License: MIT - * traitlets (5.14.3) - License: BSD-3-Clause - * typing-extensions (4.12.2) - License: PSF-2.0 - * tzdata (2024.2) - License: Apache-2.0 - * urllib3 (2.3.0) - License: MIT - * wcwidth (0.2.13) - License: MIT - * websocket-client (1.8.0) - License: Apache-2.0 - * yarl (1.18.3) - License: Apache-2.0 - -Build-time and bundled command-line tools ------------------------------------------ - * poetry (2.3.2) - License: MIT - Build-time dependency manager. - * kubectl (v1.23.0) - License: Apache-2.0 - Downloaded from dl.k8s.io at build time. - * kustomize (v4.5.7) - License: Apache-2.0 - kubernetes-sigs/kustomize. - * OpenWhisk CLI (wsk) (1.2.0) - License: Apache-2.0 - Apache OpenWhisk. - * Task (taskfile) (latest) - License: MIT - Installed from taskfile.dev at build time. - * curl, zip, unzip ((Debian bullseye)) - License: curl: MIT; zip/unzip: Info-ZIP - -Base operating system image ---------------------------- - * (see the FROM line of the Dockerfile) (-) - License: mixed; see WARN - Operating-system components are supplied by the upstream base image and are not - redistributed by the ASF in source form. - ------------------------------------------------------------------------------- -Attribution required by bundled components ------------------------------------------------------------------------------- - -This product includes software developed by the Apache Software -Foundation (http://www.apache.org/), including Apache OpenWhisk -components and Apache-2.0 licensed libraries listed above. - -This product includes software developed by third parties under the -MIT, BSD, ISC, PSF, MPL-2.0 and other licenses, as recorded in LICENSE. - -============================================================================== -END BUNDLED THIRD-PARTY SOFTWARE INVENTORY -============================================================================== +The Apache Software Foundation (https://www.apache.org/). + +--------------- + +openserverless-operator contains ConcurrentMapBackedCache.scala which is +derived from Spray Caching , namely from +`spray.caching.Cache` and `spray.caching.SimpleLruCache`, which was released +under an Apache 2.0 license. + +Copyright (C) 2011-2015 the spray project + +--------------- + +The openserverless-operator container image bundles the OpenWhisk CLI (wsk) +which was released under an Apache 2.0 license. + +Apache OpenWhisk +Copyright 2016-2024 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (https://www.apache.org/). + +--------------- + +The openserverless-operator container image bundles kubectl and kustomize +which were released under an Apache 2.0 license. + +Kubernetes +Copyright 2014 The Kubernetes Authors. + +This product includes software developed at +The Kubernetes Authors (https://kubernetes.io/). + +--------------- + +The openserverless-operator container image bundles the Python client library +for Kubernetes which was released under an Apache 2.0 license. + +Kubernetes Python Client +Copyright 2016 The Kubernetes Authors. + +--------------- + +The openserverless-operator container image bundles the MinIO Python SDK +which was released under an Apache 2.0 license. + +MinIO Python SDK for Amazon S3 Compatible Cloud Storage +Copyright 2015-2020 MinIO, Inc. + +--------------- + +The openserverless-operator container image bundles google-auth which was +released under an Apache 2.0 license. + +Copyright 2016 Google Inc. + +This product includes software developed at Google Inc. +(https://www.google.com/). + +--------------- + +The openserverless-operator container image bundles python-dateutil which was +released under an Apache 2.0 OR BSD-3-Clause license. + +Copyright 2017- Paul Ganssle +Copyright 2017- dateutil contributors (https://github.com/dateutil/dateutil) + +--------------- + +The openserverless-operator container image bundles certifi, a Category B +component under the MPL-2.0 license, which carries the Mozilla CA +certificate bundle. + +Copyright (c) Kenneth Reitz. + +This product includes cryptographic root certificates supplied by the +Mozilla Foundation (https://www.mozilla.org/). diff --git a/OPERATOR.md b/OPERATOR.md index 5c5a19aa..912a1bbb 100644 --- a/OPERATOR.md +++ b/OPERATOR.md @@ -75,8 +75,8 @@ spec: cron: true # tls enabled or not tls: false - # minio enabled or not - minio: true + # seaweedfs enabled or not + seaweedfs: true # start kafka kafka: true # start openwhisk invoker @@ -112,11 +112,11 @@ spec: tls: acme-registered-email: xxxxx@youremailserver.com acme-server-url: https://acme-staging-v02.api.letsencrypt.org/directory - minio: + seaweedfs: volume-size: 2 openserverless: - root-user: - root-password: + user: + password: configs: limits: actions: @@ -231,8 +231,8 @@ spec: cron: true # tls enabled or not tls: false - # minio enabled or not - minio: true + # seaweedfs enabled or not + seaweedfs: true # start kafka kafka: false # start openwhisk invoker diff --git a/Taskfile.yml b/Taskfile.yml index efc0aa97..4f88204b 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -189,10 +189,10 @@ tasks: cmds: - task: t:mongo - minio: - desc: run the minio integration test + s3: + desc: run the s3 integration test cmds: - - task: t:minio + - task: t:s3 postgres: desc: run the postgres integration test @@ -218,7 +218,7 @@ tasks: - task: hello - task: redis - task: mongo - - task: minio + - task: s3 - task: postgres config: diff --git a/TaskfileDev.yml b/TaskfileDev.yml index 5121f20a..5965ad56 100644 --- a/TaskfileDev.yml +++ b/TaskfileDev.yml @@ -66,7 +66,6 @@ tasks: CONTROLLER_TAG: "{{.CONTROLLER_TAG}}" OPERATOR_TAG: "{{.OPERATOR_TAG}}" COUCHDB_SERVICE_HOST: "localhost" - MINIO_API_HOST: "localhost" OW_CONTROLLER_HOST : "localhost" VIRTUAL_ENV: sh: poetry env info --path 2>/dev/null || echo "" @@ -91,10 +90,6 @@ tasks: couchdb-forward: - kubectl -n openserverless port-forward svc/couchdb 5985:5984 - mongodb: - - kubectl apply -f deploy/mongodb-operator - - kubectl apply -f deploy/mongodb - defin: > kubectl -n openserverless patch wsk/controller --type=merge --patch '{"metadata": {"finalizers":[] } }' diff --git a/TaskfileTest.yml b/TaskfileTest.yml index 5fd02c12..ff1c39fe 100644 --- a/TaskfileTest.yml +++ b/TaskfileTest.yml @@ -31,10 +31,10 @@ vars: CONFIG: "tests/{{.KUBE}}/{{.WHISK}}.yaml" REDIS_URI: "redis://s0meP%40ass4@redis:6379" REDIS_PASSWORD: s0meP@ass4 - MINIO_HOST: "openserverless-minio" - MINIO_PORT: 9000 - MINIO_USER: "minioadmin" - MINIO_PWD : "minioadmin" + S3_HOST: "seaweedfs" + S3_PORT: 9000 + S3_USER: "openserverless" + S3_PWD : "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG" APIKUBE: sh: kubectl config view -o json | jq -r '.clusters[0].cluster.server' @@ -162,10 +162,10 @@ tasks: - ops -wsk -i action update mongo/mongo tests/mongo.js - ops -wsk -i action invoke mongo/mongo -r | grep "hello" - minio: - - ops -wsk -i package update minio -p minio_host {{.MINIO_HOST}} -p minio_port {{.MINIO_PORT}} -p minio_user {{.MINIO_USER}} -p minio_pwd {{.MINIO_PWD}} - - ops -wsk -i action update minio/minio tests/minio.js - - ops -wsk -i action invoke minio/minio -r + s3: + - ops -wsk -i package update s3 -p s3_host {{.S3_HOST}} -p s3_port {{.S3_PORT}} -p s3_user {{.S3_USER}} -p s3_pwd {{.S3_PWD}} + - ops -wsk -i action update s3/s3 tests/s3.js + - ops -wsk -i action invoke s3/s3 -r mongo2: - ops -wsk -i project deploy --manifest tests/mongo.yaml @@ -177,21 +177,21 @@ tasks: - ops -wsk -i action update postgres/postgres tests/postgres.js - ops -wsk -i action invoke postgres/postgres -r | grep "OpenServerless Postgres is up and running!" - minio2: + s3-nuv: silent: true - desc: minio test + desc: s3 test cmds: - | - MINIO_ACCESS_KEY=$(kubectl -n openserverless get cm/config -o jsonpath='{.metadata.annotations.s3_access_key}') - MINIO_SECRET_KEY=$(kubectl -n openserverless get cm/config -o jsonpath='{.metadata.annotations.s3_secret_key}') - MINIO_HOST=$(kubectl -n openserverless get cm/config -o jsonpath='{.metadata.annotations.s3_host}') - MINIO_PORT=$(kubectl -n openserverless get cm/config -o jsonpath='{.metadata.annotations.s3_port}') - MINIO_BUCKET_DATA=$(kubectl -n openserverless get cm/config -o jsonpath='{.metadata.annotations.s3_bucket_data}') - MINIO_BUCKET_WEB=$(kubectl -n openserverless get cm/config -o jsonpath='{.metadata.annotations.s3_bucket_static}') - ops -wsk -i action update minio/minio-nuv tests/minio-nuv.js \ - -p minio_access "$MINIO_ACCESS_KEY" \ - -p minio_secret "$MINIO_SECRET_KEY" \ - -p minio_host "$MINIO_HOST" \ - -p minio_port "$MINIO_PORT" \ - -p minio_data "$MINIO_BUCKET_DATA" - - ops -wsk -i action invoke minio/minio-nuv -r + S3_ACCESS_KEY=$(kubectl -n openserverless get cm/config -o jsonpath='{.metadata.annotations.s3_access_key}') + S3_SECRET_KEY=$(kubectl -n openserverless get cm/config -o jsonpath='{.metadata.annotations.s3_secret_key}') + S3_HOST=$(kubectl -n openserverless get cm/config -o jsonpath='{.metadata.annotations.s3_host}') + S3_PORT=$(kubectl -n openserverless get cm/config -o jsonpath='{.metadata.annotations.s3_port}') + S3_BUCKET_DATA=$(kubectl -n openserverless get cm/config -o jsonpath='{.metadata.annotations.s3_bucket_data}') + S3_BUCKET_WEB=$(kubectl -n openserverless get cm/config -o jsonpath='{.metadata.annotations.s3_bucket_static}') + ops -wsk -i action update s3/s3-nuv tests/s3-nuv.js \ + -p s3_access "$S3_ACCESS_KEY" \ + -p s3_secret "$S3_SECRET_KEY" \ + -p s3_host "$S3_HOST" \ + -p s3_port "$S3_PORT" \ + -p s3_data "$S3_BUCKET_DATA" + - ops -wsk -i action invoke s3/s3-nuv -r diff --git a/actions/Taskfile.yml b/actions/Taskfile.yml index 70b8cef0..6ce11216 100644 --- a/actions/Taskfile.yml +++ b/actions/Taskfile.yml @@ -35,13 +35,13 @@ vars: CDB_PASSWORD: sh: kubectl -n openserverless get wsk/controller -o jsonpath='{.spec.couchdb.admin.password}' - NUV_MINIO_AUTH: - sh: kubectl -n openserverless get wsk/controller -o jsonpath='{.spec.minio.openserverless.password}' + NUV_S3_AUTH: + sh: kubectl -n openserverless get wsk/controller -o jsonpath='{.spec.seaweedfs.openserverless.password}' CDB_HOST: couchdb - MINIO_HOST: minio.openserverless.svc.cluster.local - MINIO_PORT: 9000 + S3_HOST: seaweedfs.openserverless.svc.cluster.local + S3_PORT: 9000 #GIHTUB_SA_TOKEN: # sh: kubectl -n openserverless get secrets/openserverless-github-secret -o jsonpath='{.data.token}' @@ -94,7 +94,7 @@ tasks: content:prepare: - |- mkdir -p content/common - cp ./common/minio_util.py content/common + cp ./common/s3_util.py content/common - |- cd content rm -f ../{{.DEPLOY}}/content.zip @@ -104,8 +104,8 @@ tasks: - task: content:prepare - > {{.WSKSYS}} package update nuv - -p minio_host {{.MINIO_HOST}} - -p minio_port {{.MINIO_PORT}} + -p s3_host {{.S3_HOST}} + -p s3_port {{.S3_PORT}} - > {{.WSKSYS}} action update nuv/content {{.DEPLOY}}/content.zip --kind python:sys --web true @@ -160,29 +160,29 @@ tasks: - > {{.WSKSYS}} action get nuv/psql --url - devel:minio:prepare: + devel:s3:prepare: - |- - mkdir -p devel/minio/openserverless - mkdir -p devel/minio/common - cp ./common/*.py devel/minio/common - cp ../openserverless/config.py ../openserverless/couchdb_util.py devel/minio/openserverless + mkdir -p devel/s3/openserverless + mkdir -p devel/s3/common + cp ./common/*.py devel/s3/common + cp ../openserverless/config.py ../openserverless/couchdb_util.py devel/s3/openserverless - |- - cd devel/minio - rm -f ../../{{.DEPLOY}}/minio.zip - zip -r ../../{{.DEPLOY}}/minio.zip * + cd devel/s3 + rm -f ../../{{.DEPLOY}}/s3.zip + zip -r ../../{{.DEPLOY}}/s3.zip * - devel:minio:deploy: - - task: devel:minio:prepare + devel:s3:deploy: + - task: devel:s3:prepare - > {{.WSKSYS}} package update nuv -p couchdb_user "{{.CDB_USER}}" -p couchdb_password "{{.CDB_PASSWORD}}" -p couchdb_host "{{.CDB_HOST}}" - > - {{.WSKSYS}} action update nuv/minio - {{.DEPLOY}}/minio.zip --kind python:sys --web raw + {{.WSKSYS}} action update nuv/s3 + {{.DEPLOY}}/s3.zip --kind python:sys --web raw - > - {{.WSKSYS}} action get nuv/minio --url + {{.WSKSYS}} action get nuv/s3 --url devel:upload:prepare: - |- @@ -270,23 +270,23 @@ tasks: content:webtest: - > - curl -X PUT -T ../openserverless/templates/index.html -H "minioauth: {{.NUV_MINIO_AUTH}}" {{.APIHOST}}/api/v1/web/whisk-system/nuv/content/openserverless/index2.html + curl -X PUT -T ../openserverless/templates/index.html -H "s3auth: {{.NUV_S3_AUTH}}" {{.APIHOST}}/api/v1/web/whisk-system/nuv/content/openserverless/index2.html content:webtest2: - > - curl -X PUT -T ../openserverless/templates/content.html -H "minioauth: {{.NUV_MINIO_AUTH}}" {{.APIHOST}}/api/v1/web/whisk-system/nuv/content/openserverless/tidy/content.html + curl -X PUT -T ../openserverless/templates/content.html -H "s3auth: {{.NUV_S3_AUTH}}" {{.APIHOST}}/api/v1/web/whisk-system/nuv/content/openserverless/tidy/content.html content:yaml: - > - curl -X PUT -T ../openserverless/templates/couchdb-init.yaml -H "minioauth: {{.NUV_MINIO_AUTH}}" {{.APIHOST}}/api/v1/web/whisk-system/nuv/content/openserverless/yaml/couchdb-init.yaml + curl -X PUT -T ../openserverless/templates/couchdb-init.yaml -H "s3auth: {{.NUV_S3_AUTH}}" {{.APIHOST}}/api/v1/web/whisk-system/nuv/content/openserverless/yaml/couchdb-init.yaml content-delete:yaml: - > - curl -X DELETE -H "minioauth: {{.NUV_MINIO_AUTH}}" {{.APIHOST}}/api/v1/web/whisk-system/nuv/content/openserverless/yaml/couchdb-init.yaml + curl -X DELETE -H "s3auth: {{.NUV_S3_AUTH}}" {{.APIHOST}}/api/v1/web/whisk-system/nuv/content/openserverless/yaml/couchdb-init.yaml content:chess: - > - curl -X PUT -T ../openserverless/templates/content.html -H "minioauth: {{.NUV_MINIO_AUTH}}" {{.APIHOST}}/api/v1/web/whisk-system/nuv/content/openserverless/chess/index.html + curl -X PUT -T ../openserverless/templates/content.html -H "s3auth: {{.NUV_S3_AUTH}}" {{.APIHOST}}/api/v1/web/whisk-system/nuv/content/openserverless/chess/index.html devel:redis:set: - > @@ -312,25 +312,25 @@ tasks: - > curl {{.APIHOST}}/api/v1/web/whisk-system/nuv/psql -H "x-impersonate-auth: Y2JkNjgwNzUtZGFjMi00NzVlLThjMDctZDYyYTMwYzdlNjgzOjEyM3pPM3haQ0xyTU42djJCS0sxZFhZRnBYbFBrY2NPRnFtMTJDZEFzTWdSVTRWck5aOWx5R1ZDR3VNREdJd1A=" -H "Content-Type: application/json" -d@../demos/devel/psql_create3.json - devel:minio:ls: + devel:s3:ls: - > - curl {{.APIHOST}}/api/v1/web/whisk-system/nuv/minio -H "x-impersonate-auth: Y2JkNjgwNzUtZGFjMi00NzVlLThjMDctZDYyYTMwYzdlNjgzOjEyM3pPM3haQ0xyTU42djJCS0sxZFhZRnBYbFBrY2NPRnFtMTJDZEFzTWdSVTRWck5aOWx5R1ZDR3VNREdJd1A=" -H "Content-Type: application/json" -d@../demos/devel/minio_buckets.json + curl {{.APIHOST}}/api/v1/web/whisk-system/nuv/s3 -H "x-impersonate-auth: Y2JkNjgwNzUtZGFjMi00NzVlLThjMDctZDYyYTMwYzdlNjgzOjEyM3pPM3haQ0xyTU42djJCS0sxZFhZRnBYbFBrY2NPRnFtMTJDZEFzTWdSVTRWck5aOWx5R1ZDR3VNREdJd1A=" -H "Content-Type: application/json" -d@../demos/devel/s3_buckets.json - devel:minio:lsb: + devel:s3:lsb: - > - curl {{.APIHOST}}/api/v1/web/whisk-system/nuv/minio -H "x-impersonate-auth: Y2JkNjgwNzUtZGFjMi00NzVlLThjMDctZDYyYTMwYzdlNjgzOjEyM3pPM3haQ0xyTU42djJCS0sxZFhZRnBYbFBrY2NPRnFtMTJDZEFzTWdSVTRWck5aOWx5R1ZDR3VNREdJd1A=" -H "Content-Type: application/json" -d@../demos/devel/minio_ls.json + curl {{.APIHOST}}/api/v1/web/whisk-system/nuv/s3 -H "x-impersonate-auth: Y2JkNjgwNzUtZGFjMi00NzVlLThjMDctZDYyYTMwYzdlNjgzOjEyM3pPM3haQ0xyTU42djJCS0sxZFhZRnBYbFBrY2NPRnFtMTJDZEFzTWdSVTRWck5aOWx5R1ZDR3VNREdJd1A=" -H "Content-Type: application/json" -d@../demos/devel/s3_ls.json - devel:minio:rm: + devel:s3:rm: - > - curl {{.APIHOST}}/api/v1/web/whisk-system/nuv/minio -H "x-impersonate-auth: Y2JkNjgwNzUtZGFjMi00NzVlLThjMDctZDYyYTMwYzdlNjgzOjEyM3pPM3haQ0xyTU42djJCS0sxZFhZRnBYbFBrY2NPRnFtMTJDZEFzTWdSVTRWck5aOWx5R1ZDR3VNREdJd1A=" -H "Content-Type: application/json" -d@../demos/devel/minio_rm.json + curl {{.APIHOST}}/api/v1/web/whisk-system/nuv/s3 -H "x-impersonate-auth: Y2JkNjgwNzUtZGFjMi00NzVlLThjMDctZDYyYTMwYzdlNjgzOjEyM3pPM3haQ0xyTU42djJCS0sxZFhZRnBYbFBrY2NPRnFtMTJDZEFzTWdSVTRWck5aOWx5R1ZDR3VNREdJd1A=" -H "Content-Type: application/json" -d@../demos/devel/s3_rm.json - devel:minio:mv: + devel:s3:mv: - > - curl {{.APIHOST}}/api/v1/web/whisk-system/nuv/minio -H "x-impersonate-auth: Y2JkNjgwNzUtZGFjMi00NzVlLThjMDctZDYyYTMwYzdlNjgzOjEyM3pPM3haQ0xyTU42djJCS0sxZFhZRnBYbFBrY2NPRnFtMTJDZEFzTWdSVTRWck5aOWx5R1ZDR3VNREdJd1A=" -H "Content-Type: application/json" -d@../demos/devel/minio_mv.json + curl {{.APIHOST}}/api/v1/web/whisk-system/nuv/s3 -H "x-impersonate-auth: Y2JkNjgwNzUtZGFjMi00NzVlLThjMDctZDYyYTMwYzdlNjgzOjEyM3pPM3haQ0xyTU42djJCS0sxZFhZRnBYbFBrY2NPRnFtMTJDZEFzTWdSVTRWck5aOWx5R1ZDR3VNREdJd1A=" -H "Content-Type: application/json" -d@../demos/devel/s3_mv.json - devel:minio:cp: + devel:s3:cp: - > - curl {{.APIHOST}}/api/v1/web/whisk-system/nuv/minio -H "x-impersonate-auth: Y2JkNjgwNzUtZGFjMi00NzVlLThjMDctZDYyYTMwYzdlNjgzOjEyM3pPM3haQ0xyTU42djJCS0sxZFhZRnBYbFBrY2NPRnFtMTJDZEFzTWdSVTRWck5aOWx5R1ZDR3VNREdJd1A=" -H "Content-Type: application/json" -d@../demos/devel/minio_cp.json + curl {{.APIHOST}}/api/v1/web/whisk-system/nuv/s3 -H "x-impersonate-auth: Y2JkNjgwNzUtZGFjMi00NzVlLThjMDctZDYyYTMwYzdlNjgzOjEyM3pPM3haQ0xyTU42djJCS0sxZFhZRnBYbFBrY2NPRnFtMTJDZEFzTWdSVTRWck5aOWx5R1ZDR3VNREdJd1A=" -H "Content-Type: application/json" -d@../demos/devel/s3_cp.json devel:upload:webtest: - > @@ -374,7 +374,7 @@ tasks: deploy:devel: - task: devel:redis:deploy - - task: devel:minio:deploy + - task: devel:s3:deploy - task: devel:psql:deploy - task: devel:upload:deploy - task: devel:ferretdb:deploy diff --git a/actions/devel/ferretdb/common/minio_util.py b/actions/common/s3_util.py similarity index 95% rename from actions/devel/ferretdb/common/minio_util.py rename to actions/common/s3_util.py index e1e8f907..ae758b0b 100644 --- a/actions/devel/ferretdb/common/minio_util.py +++ b/actions/common/s3_util.py @@ -36,11 +36,11 @@ def extract_mimetype(file): def build_mo_client(host, port, access_key, secret_key): """ - Creates an Minio client pointing to the given MINIO HOST - :param host, minio host - :param port, minio api port normally it is the 9000 + Creates an S3 client pointing to the given S3-compatible HOST + :param host, s3 host + :param port, s3 api port normally it is the 9000 :param access_key user we are representing - :param secret_key to access minio + :param secret_key to access the object store """ mo_client = Minio(f"{host}:{port}",access_key=access_key,secret_key=secret_key,secure=False) return mo_client @@ -119,7 +119,7 @@ def get_random_string(length): def rm_file(mo_client, bucket, file): """ Remove a file from a bucket - :parma mo_client a minio client instance to execute the command + :parma mo_client a S3 client instance to execute the command :param bucket the bucket name :param file the file name :return True if the file has been removed, False otherwise diff --git a/actions/content/__main__.py b/actions/content/__main__.py index 305864dd..90da7a94 100644 --- a/actions/content/__main__.py +++ b/actions/content/__main__.py @@ -21,7 +21,7 @@ import string import mimetypes -import common.minio_util as mutil +import common.s3_util as mutil def build_error(message: str): return { @@ -90,9 +90,9 @@ def _delete(mo_client, upload_data): def main(args): """ - Simple actions to upload/remove a files into a minio bucket. + Simple actions to upload/remove a files into an S3 bucket. The action it is supposed to receive a path param similar to //? - and will store/remove the given path under the -web bucket using minio client + and will store/remove the given path under the -web bucket using an S3 client """ print(args) @@ -102,19 +102,19 @@ def main(args): if(method.lower() not in ['put','delete'] ): return build_error(f"invalid request, HTTP verb {method} is not supported") - if('minioauth' not in headers): - return build_error("invalid request, missing mandatory header: minioauth") + if('s3auth' not in headers): + return build_error("invalid request, missing mandatory header: s3auth") upload_data = process_path_param(args['__ow_path']) if 'user' not in upload_data and 'filename' not in upload_data: return build_error("invalid request, username and/or filename path error") - minio_host = args['minio_host'] - minio_port = args['minio_port'] - auth = headers['minioauth'] + s3_host = args['s3_host'] + s3_port = args['s3_port'] + auth = headers['s3auth'] - mo_client = mutil.build_mo_client(minio_host, minio_port,upload_data['user'], auth) + mo_client = mutil.build_mo_client(s3_host, s3_port,upload_data['user'], auth) if method.lower() in 'put': if(len(args['__ow_body']) == 0): diff --git a/actions/common/minio_util.py b/actions/content/common/s3_util.py similarity index 95% rename from actions/common/minio_util.py rename to actions/content/common/s3_util.py index e1e8f907..ae758b0b 100644 --- a/actions/common/minio_util.py +++ b/actions/content/common/s3_util.py @@ -36,11 +36,11 @@ def extract_mimetype(file): def build_mo_client(host, port, access_key, secret_key): """ - Creates an Minio client pointing to the given MINIO HOST - :param host, minio host - :param port, minio api port normally it is the 9000 + Creates an S3 client pointing to the given S3-compatible HOST + :param host, s3 host + :param port, s3 api port normally it is the 9000 :param access_key user we are representing - :param secret_key to access minio + :param secret_key to access the object store """ mo_client = Minio(f"{host}:{port}",access_key=access_key,secret_key=secret_key,secure=False) return mo_client @@ -119,7 +119,7 @@ def get_random_string(length): def rm_file(mo_client, bucket, file): """ Remove a file from a bucket - :parma mo_client a minio client instance to execute the command + :parma mo_client a S3 client instance to execute the command :param bucket the bucket name :param file the file name :return True if the file has been removed, False otherwise diff --git a/actions/devel/download/__main__.py b/actions/devel/download/__main__.py index c56ba119..aa9e9e88 100644 --- a/actions/devel/download/__main__.py +++ b/actions/devel/download/__main__.py @@ -22,7 +22,7 @@ import mimetypes import io -import common.minio_util as mutil +import common.s3_util as mutil import common.util as ut from common.authorize import Authorize @@ -73,7 +73,7 @@ def main(args): """ Action implementing a generic download wrapper for the nuv devel plugin. The invoker must provide a x-impersonate-auth header containing the Openwhisk BASIC authentication of the wsku/user the action should impersonate when calling this action. The upload action it is supposed to receive a path param similar to // - and will attempt to retrieve the given path under the given MINIO . The bucket must exists and the impersonated user must have read permission on it. + and will attempt to retrieve the given path under the given S3 . The bucket must exists and the impersonated user must have read permission on it. """ headers = args['__ow_headers'] if('x-impersonate-auth' not in headers): @@ -88,7 +88,7 @@ def main(args): print(f"processing request to download {download_data['path']} from bucket {download_data['bucket']}") user_data = Authorize(args['couchdb_host'],args['couchdb_user'],args['couchdb_password']).login(headers['x-impersonate-auth']) - mo_client = mutil.build_mo_client(ut.get_env_value(user_data,"MINIO_HOST"), ut.get_env_value(user_data,"MINIO_PORT"),ut.get_env_value(user_data,"MINIO_ACCESS_KEY") , ut.get_env_value(user_data,"MINIO_SECRET_KEY")) + mo_client = mutil.build_mo_client(ut.get_env_value(user_data,"S3_HOST"), ut.get_env_value(user_data,"S3_PORT"),ut.get_env_value(user_data,"S3_ACCESS_KEY") , ut.get_env_value(user_data,"S3_SECRET_KEY")) # see https://urllib3.readthedocs.io/en/latest/reference/urllib3.response.html for the format response = mo_client.get_object(bucket_name = download_data['bucket'], object_name= download_data['path']) diff --git a/actions/content/common/minio_util.py b/actions/devel/download/common/s3_util.py similarity index 95% rename from actions/content/common/minio_util.py rename to actions/devel/download/common/s3_util.py index e1e8f907..ae758b0b 100644 --- a/actions/content/common/minio_util.py +++ b/actions/devel/download/common/s3_util.py @@ -36,11 +36,11 @@ def extract_mimetype(file): def build_mo_client(host, port, access_key, secret_key): """ - Creates an Minio client pointing to the given MINIO HOST - :param host, minio host - :param port, minio api port normally it is the 9000 + Creates an S3 client pointing to the given S3-compatible HOST + :param host, s3 host + :param port, s3 api port normally it is the 9000 :param access_key user we are representing - :param secret_key to access minio + :param secret_key to access the object store """ mo_client = Minio(f"{host}:{port}",access_key=access_key,secret_key=secret_key,secure=False) return mo_client @@ -119,7 +119,7 @@ def get_random_string(length): def rm_file(mo_client, bucket, file): """ Remove a file from a bucket - :parma mo_client a minio client instance to execute the command + :parma mo_client a S3 client instance to execute the command :param bucket the bucket name :param file the file name :return True if the file has been removed, False otherwise diff --git a/actions/devel/download/common/minio_util.py b/actions/devel/ferretdb/common/s3_util.py similarity index 95% rename from actions/devel/download/common/minio_util.py rename to actions/devel/ferretdb/common/s3_util.py index e1e8f907..ae758b0b 100644 --- a/actions/devel/download/common/minio_util.py +++ b/actions/devel/ferretdb/common/s3_util.py @@ -36,11 +36,11 @@ def extract_mimetype(file): def build_mo_client(host, port, access_key, secret_key): """ - Creates an Minio client pointing to the given MINIO HOST - :param host, minio host - :param port, minio api port normally it is the 9000 + Creates an S3 client pointing to the given S3-compatible HOST + :param host, s3 host + :param port, s3 api port normally it is the 9000 :param access_key user we are representing - :param secret_key to access minio + :param secret_key to access the object store """ mo_client = Minio(f"{host}:{port}",access_key=access_key,secret_key=secret_key,secure=False) return mo_client @@ -119,7 +119,7 @@ def get_random_string(length): def rm_file(mo_client, bucket, file): """ Remove a file from a bucket - :parma mo_client a minio client instance to execute the command + :parma mo_client a S3 client instance to execute the command :param bucket the bucket name :param file the file name :return True if the file has been removed, False otherwise diff --git a/actions/devel/minio/common/minio_util.py b/actions/devel/minio/common/minio_util.py deleted file mode 100644 index e1e8f907..00000000 --- a/actions/devel/minio/common/minio_util.py +++ /dev/null @@ -1,182 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import os -import base64 -import random -import string -import mimetypes - -from minio import Minio -from minio.error import S3Error -from minio.commonconfig import CopySource - -def extract_mimetype(file): - mimetype, _ = mimetypes.guess_type(file) - - if mimetype is None: - return "application/octet-stream" - else: - return mimetype - -def build_mo_client(host, port, access_key, secret_key): - """ - Creates an Minio client pointing to the given MINIO HOST - :param host, minio host - :param port, minio api port normally it is the 9000 - :param access_key user we are representing - :param secret_key to access minio - """ - mo_client = Minio(f"{host}:{port}",access_key=access_key,secret_key=secret_key,secure=False) - return mo_client - -def upload_file(mo_client, file, bucket, object_name=None): - """Upload a file to an S3 bucket - - :param file_name: File to upload - :param bucket: Bucket to upload to - :param object_name: S3 object name. If not specified then file_name is used - :return: a tuple with the Booleand str, - """ - - # If S3 object_name was not specified, use file_name - if object_name is None: - object_name = os.path.basename(file) - - print(f"uploading {object_name} into bucket {bucket} from tmp_file {file}") - - # Upload the file - try: - mimetype = extract_mimetype(object_name) - response = mo_client.fput_object(bucket_name=bucket,object_name=object_name,file_path=file,content_type=mimetype) - if response._object_name: - return True, response._object_name - except Exception as e: - print(e) - return False, str(e) - return False, "KO" - -def prepare_file_upload(username, filename, file_content_as_b64): - """ Creates a tmp area for the given user where the uploaded file will be stored under a random generated name - the fully qualified filename is taken into account only on the corresponding destination bucket. - param: username - param: filename - param: file_content_as_b64 - return: a file object pointing to the tmp file - """ - try: - user_tmp_folder = f"/tmp/{username}" - if not os.path.exists(user_tmp_folder): - os.makedirs(user_tmp_folder) - print(f"added tmp folder {user_tmp_folder}") - - delete_files_in_directory_and_subdirectories(user_tmp_folder) - rnd_filename = get_random_string(20) - tmp_file = f"{user_tmp_folder}/{rnd_filename}" - - with open(tmp_file, "wb") as f: - file_content=base64.b64decode(file_content_as_b64) - f.write(file_content) - - if os.path.exists(tmp_file): - print(f"{tmp_file} stored successfully.") - return tmp_file - else: - return None - except Exception as e: - print("error preparing tmp_files",e) - return None - -def delete_files_in_directory_and_subdirectories(directory_path): - try: - for root, dirs, files in os.walk(directory_path): - for file in files: - file_path = os.path.join(root, file) - os.remove(file_path) - print("All files and subdirectories deleted successfully.") - except OSError: - print("Error occurred while deleting files and subdirectories.") - -def get_random_string(length): - # choose from all lowercase letter - letters = string.ascii_lowercase - return ''.join(random.choice(letters) for i in range(length)) - -def rm_file(mo_client, bucket, file): - """ Remove a file from a bucket - :parma mo_client a minio client instance to execute the command - :param bucket the bucket name - :param file the file name - :return True if the file has been removed, False otherwise - """ - - try: - mo_client.remove_object(bucket, file) - return True - except Exception as e: - print(e) - return False - return False - -def cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): - """Copy a file between two buckets - - :param orig_bucket: Origin bucket - :param orig_file: Origin file - :param dest_bucket: Destination bucket - :param dest_file: Destination file - :return: name of the destination file is everything is ok. None otherwise - """ - print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") - try: - # copy an object from a bucket to another. - cp_result = mo_client.copy_object( - dest_bucket, - dest_file, - CopySource(orig_bucket, orig_file) - ) - if cp_result.object_name: - return cp_result.object_name - except Exception as e: - print(e) - return None - return None - -def mv_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): - """Move a file between two buckets - - :param orig_bucket: Origin bucket - :param orig_file: Origin file - :param dest_bucket: Destination bucket - :param dest_file: Destination file - :return: name of the destination file is everything is ok. None otherwise - """ - print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") - - # Upload the file - try: - # mv an object from a bucket to another. - object_name = cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file) - if object_name: - rm_file(mo_client,orig_bucket,orig_file) - return object_name - except Exception as e: - print(e) - return None - return None - diff --git a/actions/devel/psql/common/minio_util.py b/actions/devel/psql/common/minio_util.py deleted file mode 100644 index e1e8f907..00000000 --- a/actions/devel/psql/common/minio_util.py +++ /dev/null @@ -1,182 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import os -import base64 -import random -import string -import mimetypes - -from minio import Minio -from minio.error import S3Error -from minio.commonconfig import CopySource - -def extract_mimetype(file): - mimetype, _ = mimetypes.guess_type(file) - - if mimetype is None: - return "application/octet-stream" - else: - return mimetype - -def build_mo_client(host, port, access_key, secret_key): - """ - Creates an Minio client pointing to the given MINIO HOST - :param host, minio host - :param port, minio api port normally it is the 9000 - :param access_key user we are representing - :param secret_key to access minio - """ - mo_client = Minio(f"{host}:{port}",access_key=access_key,secret_key=secret_key,secure=False) - return mo_client - -def upload_file(mo_client, file, bucket, object_name=None): - """Upload a file to an S3 bucket - - :param file_name: File to upload - :param bucket: Bucket to upload to - :param object_name: S3 object name. If not specified then file_name is used - :return: a tuple with the Booleand str, - """ - - # If S3 object_name was not specified, use file_name - if object_name is None: - object_name = os.path.basename(file) - - print(f"uploading {object_name} into bucket {bucket} from tmp_file {file}") - - # Upload the file - try: - mimetype = extract_mimetype(object_name) - response = mo_client.fput_object(bucket_name=bucket,object_name=object_name,file_path=file,content_type=mimetype) - if response._object_name: - return True, response._object_name - except Exception as e: - print(e) - return False, str(e) - return False, "KO" - -def prepare_file_upload(username, filename, file_content_as_b64): - """ Creates a tmp area for the given user where the uploaded file will be stored under a random generated name - the fully qualified filename is taken into account only on the corresponding destination bucket. - param: username - param: filename - param: file_content_as_b64 - return: a file object pointing to the tmp file - """ - try: - user_tmp_folder = f"/tmp/{username}" - if not os.path.exists(user_tmp_folder): - os.makedirs(user_tmp_folder) - print(f"added tmp folder {user_tmp_folder}") - - delete_files_in_directory_and_subdirectories(user_tmp_folder) - rnd_filename = get_random_string(20) - tmp_file = f"{user_tmp_folder}/{rnd_filename}" - - with open(tmp_file, "wb") as f: - file_content=base64.b64decode(file_content_as_b64) - f.write(file_content) - - if os.path.exists(tmp_file): - print(f"{tmp_file} stored successfully.") - return tmp_file - else: - return None - except Exception as e: - print("error preparing tmp_files",e) - return None - -def delete_files_in_directory_and_subdirectories(directory_path): - try: - for root, dirs, files in os.walk(directory_path): - for file in files: - file_path = os.path.join(root, file) - os.remove(file_path) - print("All files and subdirectories deleted successfully.") - except OSError: - print("Error occurred while deleting files and subdirectories.") - -def get_random_string(length): - # choose from all lowercase letter - letters = string.ascii_lowercase - return ''.join(random.choice(letters) for i in range(length)) - -def rm_file(mo_client, bucket, file): - """ Remove a file from a bucket - :parma mo_client a minio client instance to execute the command - :param bucket the bucket name - :param file the file name - :return True if the file has been removed, False otherwise - """ - - try: - mo_client.remove_object(bucket, file) - return True - except Exception as e: - print(e) - return False - return False - -def cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): - """Copy a file between two buckets - - :param orig_bucket: Origin bucket - :param orig_file: Origin file - :param dest_bucket: Destination bucket - :param dest_file: Destination file - :return: name of the destination file is everything is ok. None otherwise - """ - print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") - try: - # copy an object from a bucket to another. - cp_result = mo_client.copy_object( - dest_bucket, - dest_file, - CopySource(orig_bucket, orig_file) - ) - if cp_result.object_name: - return cp_result.object_name - except Exception as e: - print(e) - return None - return None - -def mv_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): - """Move a file between two buckets - - :param orig_bucket: Origin bucket - :param orig_file: Origin file - :param dest_bucket: Destination bucket - :param dest_file: Destination file - :return: name of the destination file is everything is ok. None otherwise - """ - print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") - - # Upload the file - try: - # mv an object from a bucket to another. - object_name = cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file) - if object_name: - rm_file(mo_client,orig_bucket,orig_file) - return object_name - except Exception as e: - print(e) - return None - return None - diff --git a/actions/devel/psql/common/s3_util.py b/actions/devel/psql/common/s3_util.py new file mode 100644 index 00000000..e89f34f4 --- /dev/null +++ b/actions/devel/psql/common/s3_util.py @@ -0,0 +1,182 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import os +import base64 +import random +import string +import mimetypes + +from minio import Minio +from minio.error import S3Error +from minio.commonconfig import CopySource + +def extract_mimetype(file): + mimetype, _ = mimetypes.guess_type(file) + + if mimetype is None: + return "application/octet-stream" + else: + return mimetype + +def build_mo_client(host, port, access_key, secret_key): + """ + Creates an S3 client pointing to the given S3-compatible HOST + :param host, s3 host + :param port, s3 api port normally it is the 9000 + :param access_key user we are representing + :param secret_key to access the object store + """ + mo_client = Minio(f"{host}:{port}",access_key=access_key,secret_key=secret_key,secure=False) + return mo_client + +def upload_file(mo_client, file, bucket, object_name=None): + """Upload a file to an S3 bucket + + :param file_name: File to upload + :param bucket: Bucket to upload to + :param object_name: S3 object name. If not specified then file_name is used + :return: a tuple with the Booleand str, + """ + + # If S3 object_name was not specified, use file_name + if object_name is None: + object_name = os.path.basename(file) + + print(f"uploading {object_name} into bucket {bucket} from tmp_file {file}") + + # Upload the file + try: + mimetype = extract_mimetype(object_name) + response = mo_client.fput_object(bucket_name=bucket,object_name=object_name,file_path=file,content_type=mimetype) + if response._object_name: + return True, response._object_name + except Exception as e: + print(e) + return False, str(e) + return False, "KO" + +def prepare_file_upload(username, filename, file_content_as_b64): + """ Creates a tmp area for the given user where the uploaded file will be stored under a random generated name + the fully qualified filename is taken into account only on the corresponding destination bucket. + param: username + param: filename + param: file_content_as_b64 + return: a file object pointing to the tmp file + """ + try: + user_tmp_folder = f"/tmp/{username}" + if not os.path.exists(user_tmp_folder): + os.makedirs(user_tmp_folder) + print(f"added tmp folder {user_tmp_folder}") + + delete_files_in_directory_and_subdirectories(user_tmp_folder) + rnd_filename = get_random_string(20) + tmp_file = f"{user_tmp_folder}/{rnd_filename}" + + with open(tmp_file, "wb") as f: + file_content=base64.b64decode(file_content_as_b64) + f.write(file_content) + + if os.path.exists(tmp_file): + print(f"{tmp_file} stored successfully.") + return tmp_file + else: + return None + except Exception as e: + print("error preparing tmp_files",e) + return None + +def delete_files_in_directory_and_subdirectories(directory_path): + try: + for root, dirs, files in os.walk(directory_path): + for file in files: + file_path = os.path.join(root, file) + os.remove(file_path) + print("All files and subdirectories deleted successfully.") + except OSError: + print("Error occurred while deleting files and subdirectories.") + +def get_random_string(length): + # choose from all lowercase letter + letters = string.ascii_lowercase + return ''.join(random.choice(letters) for i in range(length)) + +def rm_file(mo_client, bucket, file): + """ Remove a file from a bucket + :parma mo_client a S3 client instance to execute the command + :param bucket the bucket name + :param file the file name + :return True if the file has been removed, False otherwise + """ + + try: + mo_client.remove_object(bucket, file) + return True + except Exception as e: + print(e) + return False + return False + +def cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): + """Copy a file between two buckets + + :param orig_bucket: Origin bucket + :param orig_file: Origin file + :param dest_bucket: Destination bucket + :param dest_file: Destination file + :return: name of the destination file is everything is ok. None otherwise + """ + print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") + try: + # copy an object from a bucket to another. + cp_result = mo_client.copy_object( + dest_bucket, + dest_file, + CopySource(orig_bucket, orig_file) + ) + if cp_result.object_name: + return cp_result.object_name + except Exception as e: + print(e) + return None + return None + +def mv_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): + """Move a file between two buckets + + :param orig_bucket: Origin bucket + :param orig_file: Origin file + :param dest_bucket: Destination bucket + :param dest_file: Destination file + :return: name of the destination file is everything is ok. None otherwise + """ + print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") + + # Upload the file + try: + # mv an object from a bucket to another. + object_name = cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file) + if object_name: + rm_file(mo_client,orig_bucket,orig_file) + return object_name + except Exception as e: + print(e) + return None + return None + diff --git a/actions/devel/redis/common/minio_util.py b/actions/devel/redis/common/minio_util.py deleted file mode 100644 index e1e8f907..00000000 --- a/actions/devel/redis/common/minio_util.py +++ /dev/null @@ -1,182 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import os -import base64 -import random -import string -import mimetypes - -from minio import Minio -from minio.error import S3Error -from minio.commonconfig import CopySource - -def extract_mimetype(file): - mimetype, _ = mimetypes.guess_type(file) - - if mimetype is None: - return "application/octet-stream" - else: - return mimetype - -def build_mo_client(host, port, access_key, secret_key): - """ - Creates an Minio client pointing to the given MINIO HOST - :param host, minio host - :param port, minio api port normally it is the 9000 - :param access_key user we are representing - :param secret_key to access minio - """ - mo_client = Minio(f"{host}:{port}",access_key=access_key,secret_key=secret_key,secure=False) - return mo_client - -def upload_file(mo_client, file, bucket, object_name=None): - """Upload a file to an S3 bucket - - :param file_name: File to upload - :param bucket: Bucket to upload to - :param object_name: S3 object name. If not specified then file_name is used - :return: a tuple with the Booleand str, - """ - - # If S3 object_name was not specified, use file_name - if object_name is None: - object_name = os.path.basename(file) - - print(f"uploading {object_name} into bucket {bucket} from tmp_file {file}") - - # Upload the file - try: - mimetype = extract_mimetype(object_name) - response = mo_client.fput_object(bucket_name=bucket,object_name=object_name,file_path=file,content_type=mimetype) - if response._object_name: - return True, response._object_name - except Exception as e: - print(e) - return False, str(e) - return False, "KO" - -def prepare_file_upload(username, filename, file_content_as_b64): - """ Creates a tmp area for the given user where the uploaded file will be stored under a random generated name - the fully qualified filename is taken into account only on the corresponding destination bucket. - param: username - param: filename - param: file_content_as_b64 - return: a file object pointing to the tmp file - """ - try: - user_tmp_folder = f"/tmp/{username}" - if not os.path.exists(user_tmp_folder): - os.makedirs(user_tmp_folder) - print(f"added tmp folder {user_tmp_folder}") - - delete_files_in_directory_and_subdirectories(user_tmp_folder) - rnd_filename = get_random_string(20) - tmp_file = f"{user_tmp_folder}/{rnd_filename}" - - with open(tmp_file, "wb") as f: - file_content=base64.b64decode(file_content_as_b64) - f.write(file_content) - - if os.path.exists(tmp_file): - print(f"{tmp_file} stored successfully.") - return tmp_file - else: - return None - except Exception as e: - print("error preparing tmp_files",e) - return None - -def delete_files_in_directory_and_subdirectories(directory_path): - try: - for root, dirs, files in os.walk(directory_path): - for file in files: - file_path = os.path.join(root, file) - os.remove(file_path) - print("All files and subdirectories deleted successfully.") - except OSError: - print("Error occurred while deleting files and subdirectories.") - -def get_random_string(length): - # choose from all lowercase letter - letters = string.ascii_lowercase - return ''.join(random.choice(letters) for i in range(length)) - -def rm_file(mo_client, bucket, file): - """ Remove a file from a bucket - :parma mo_client a minio client instance to execute the command - :param bucket the bucket name - :param file the file name - :return True if the file has been removed, False otherwise - """ - - try: - mo_client.remove_object(bucket, file) - return True - except Exception as e: - print(e) - return False - return False - -def cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): - """Copy a file between two buckets - - :param orig_bucket: Origin bucket - :param orig_file: Origin file - :param dest_bucket: Destination bucket - :param dest_file: Destination file - :return: name of the destination file is everything is ok. None otherwise - """ - print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") - try: - # copy an object from a bucket to another. - cp_result = mo_client.copy_object( - dest_bucket, - dest_file, - CopySource(orig_bucket, orig_file) - ) - if cp_result.object_name: - return cp_result.object_name - except Exception as e: - print(e) - return None - return None - -def mv_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): - """Move a file between two buckets - - :param orig_bucket: Origin bucket - :param orig_file: Origin file - :param dest_bucket: Destination bucket - :param dest_file: Destination file - :return: name of the destination file is everything is ok. None otherwise - """ - print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") - - # Upload the file - try: - # mv an object from a bucket to another. - object_name = cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file) - if object_name: - rm_file(mo_client,orig_bucket,orig_file) - return object_name - except Exception as e: - print(e) - return None - return None - diff --git a/actions/devel/redis/common/s3_util.py b/actions/devel/redis/common/s3_util.py new file mode 100644 index 00000000..e89f34f4 --- /dev/null +++ b/actions/devel/redis/common/s3_util.py @@ -0,0 +1,182 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import os +import base64 +import random +import string +import mimetypes + +from minio import Minio +from minio.error import S3Error +from minio.commonconfig import CopySource + +def extract_mimetype(file): + mimetype, _ = mimetypes.guess_type(file) + + if mimetype is None: + return "application/octet-stream" + else: + return mimetype + +def build_mo_client(host, port, access_key, secret_key): + """ + Creates an S3 client pointing to the given S3-compatible HOST + :param host, s3 host + :param port, s3 api port normally it is the 9000 + :param access_key user we are representing + :param secret_key to access the object store + """ + mo_client = Minio(f"{host}:{port}",access_key=access_key,secret_key=secret_key,secure=False) + return mo_client + +def upload_file(mo_client, file, bucket, object_name=None): + """Upload a file to an S3 bucket + + :param file_name: File to upload + :param bucket: Bucket to upload to + :param object_name: S3 object name. If not specified then file_name is used + :return: a tuple with the Booleand str, + """ + + # If S3 object_name was not specified, use file_name + if object_name is None: + object_name = os.path.basename(file) + + print(f"uploading {object_name} into bucket {bucket} from tmp_file {file}") + + # Upload the file + try: + mimetype = extract_mimetype(object_name) + response = mo_client.fput_object(bucket_name=bucket,object_name=object_name,file_path=file,content_type=mimetype) + if response._object_name: + return True, response._object_name + except Exception as e: + print(e) + return False, str(e) + return False, "KO" + +def prepare_file_upload(username, filename, file_content_as_b64): + """ Creates a tmp area for the given user where the uploaded file will be stored under a random generated name + the fully qualified filename is taken into account only on the corresponding destination bucket. + param: username + param: filename + param: file_content_as_b64 + return: a file object pointing to the tmp file + """ + try: + user_tmp_folder = f"/tmp/{username}" + if not os.path.exists(user_tmp_folder): + os.makedirs(user_tmp_folder) + print(f"added tmp folder {user_tmp_folder}") + + delete_files_in_directory_and_subdirectories(user_tmp_folder) + rnd_filename = get_random_string(20) + tmp_file = f"{user_tmp_folder}/{rnd_filename}" + + with open(tmp_file, "wb") as f: + file_content=base64.b64decode(file_content_as_b64) + f.write(file_content) + + if os.path.exists(tmp_file): + print(f"{tmp_file} stored successfully.") + return tmp_file + else: + return None + except Exception as e: + print("error preparing tmp_files",e) + return None + +def delete_files_in_directory_and_subdirectories(directory_path): + try: + for root, dirs, files in os.walk(directory_path): + for file in files: + file_path = os.path.join(root, file) + os.remove(file_path) + print("All files and subdirectories deleted successfully.") + except OSError: + print("Error occurred while deleting files and subdirectories.") + +def get_random_string(length): + # choose from all lowercase letter + letters = string.ascii_lowercase + return ''.join(random.choice(letters) for i in range(length)) + +def rm_file(mo_client, bucket, file): + """ Remove a file from a bucket + :parma mo_client a S3 client instance to execute the command + :param bucket the bucket name + :param file the file name + :return True if the file has been removed, False otherwise + """ + + try: + mo_client.remove_object(bucket, file) + return True + except Exception as e: + print(e) + return False + return False + +def cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): + """Copy a file between two buckets + + :param orig_bucket: Origin bucket + :param orig_file: Origin file + :param dest_bucket: Destination bucket + :param dest_file: Destination file + :return: name of the destination file is everything is ok. None otherwise + """ + print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") + try: + # copy an object from a bucket to another. + cp_result = mo_client.copy_object( + dest_bucket, + dest_file, + CopySource(orig_bucket, orig_file) + ) + if cp_result.object_name: + return cp_result.object_name + except Exception as e: + print(e) + return None + return None + +def mv_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): + """Move a file between two buckets + + :param orig_bucket: Origin bucket + :param orig_file: Origin file + :param dest_bucket: Destination bucket + :param dest_file: Destination file + :return: name of the destination file is everything is ok. None otherwise + """ + print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") + + # Upload the file + try: + # mv an object from a bucket to another. + object_name = cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file) + if object_name: + rm_file(mo_client,orig_bucket,orig_file) + return object_name + except Exception as e: + print(e) + return None + return None + diff --git a/actions/devel/minio/__main__.py b/actions/devel/s3/__main__.py similarity index 94% rename from actions/devel/minio/__main__.py rename to actions/devel/s3/__main__.py index cbf52ea9..221c66fb 100644 --- a/actions/devel/minio/__main__.py +++ b/actions/devel/s3/__main__.py @@ -20,7 +20,7 @@ from common.util import is_json from common.authorize import Authorize -from command.minio import Minio +from command.s3 import S3 from common.command_data import CommandData from base64 import b64decode @@ -75,6 +75,6 @@ def main(args): try: user_data = Authorize(args['couchdb_host'],args['couchdb_user'],args['couchdb_password']).login(headers['x-impersonate-auth']) cmd = CommandData(json.loads(parse_body(args))) - return build_response(Minio(user_data).execute(cmd)) + return build_response(S3(user_data).execute(cmd)) except Exception as e: - return build_error(f"failed to execute nuv devel minio. Reason: {str(e)}") \ No newline at end of file + return build_error(f"failed to execute nuv devel s3. Reason: {str(e)}") \ No newline at end of file diff --git a/actions/devel/minio/command/minio.py b/actions/devel/s3/command/s3.py similarity index 76% rename from actions/devel/minio/command/minio.py rename to actions/devel/s3/command/s3.py index 0bc85fbe..42f8f4ef 100644 --- a/actions/devel/minio/command/minio.py +++ b/actions/devel/s3/command/s3.py @@ -17,7 +17,7 @@ # import common.util as ut -import common.minio_util as mutil +import common.s3_util as mutil import json import re @@ -25,33 +25,33 @@ from minio.commonconfig import CopySource -class Minio(): +class S3(): """ - Implementation of a Minio Command executor. It will require + Implementation of an S3 Command executor. It will require a user_data dictionary linked to a specific user. """ def __init__(self, user_data): self._user_data = user_data - self._minio_access_key= ut.get_env_value(user_data,"S3_ACCESS_KEY") - self._minio_secret_key= ut.get_env_value(user_data,"S3_SECRET_KEY") - self._minio_host= ut.get_env_value(user_data,"S3_HOST") - self._minio_port= ut.get_env_value(user_data,"S3_PORT") - self._minio_data_bucket= ut.get_env_value(user_data,"S3_DATA_BUCKET") - self._minio_static_bucket= ut.get_env_value(user_data,"S3_STATIC_BUCKET") + self._s3_access_key= ut.get_env_value(user_data,"S3_ACCESS_KEY") + self._s3_secret_key= ut.get_env_value(user_data,"S3_SECRET_KEY") + self._s3_host= ut.get_env_value(user_data,"S3_HOST") + self._s3_port= ut.get_env_value(user_data,"S3_PORT") + self._s3_data_bucket= ut.get_env_value(user_data,"S3_DATA_BUCKET") + self._s3_static_bucket= ut.get_env_value(user_data,"S3_STATIC_BUCKET") self.validate() def validate(self): """ Validate that the provided user_data contains the appropriate - metadata for being able to submit a postgres command. + metadata for being able to submit a storage command. """ - if not self._minio_access_key or not self._minio_secret_key or not self._minio_host or not self._minio_port: - raise Exception("user does not have valid MINIO environment set") + if not self._s3_access_key or not self._s3_secret_key or not self._s3_host or not self._s3_port: + raise Exception("user does not have valid S3 environment set") def _list_buckets(self, input:CommandData): print("**** listing user buckets") - mo_client = mutil.build_mo_client(self._minio_host, self._minio_port,self._minio_access_key, self._minio_secret_key) + mo_client = mutil.build_mo_client(self._s3_host, self._s3_port,self._s3_access_key, self._s3_secret_key) result = [] buckets = mo_client.list_buckets() @@ -65,7 +65,7 @@ def _list_buckets(self, input:CommandData): def _list_bucket_content(self,bucket,input:CommandData): print(f"**** listing bucket {bucket} content") - mo_client = mutil.build_mo_client(self._minio_host, self._minio_port,self._minio_access_key, self._minio_secret_key) + mo_client = mutil.build_mo_client(self._s3_host, self._s3_port,self._s3_access_key, self._s3_secret_key) result = [] objects = mo_client.list_objects(bucket_name=bucket, recursive= True) @@ -78,7 +78,7 @@ def _list_bucket_content(self,bucket,input:CommandData): def _rm_bucket_object(self,bucket,filename,input:CommandData): print(f"**** removing file {filename} inside bucket {bucket}") - mo_client = mutil.build_mo_client(self._minio_host, self._minio_port,self._minio_access_key, self._minio_secret_key) + mo_client = mutil.build_mo_client(self._s3_host, self._s3_port,self._s3_access_key, self._s3_secret_key) mo_client.remove_object(bucket, filename) result = {"result":"OK"} @@ -88,7 +88,7 @@ def _rm_bucket_object(self,bucket,filename,input:CommandData): def _mv_bucket_object(self,orig_bucket,orig_filename,dest_bucket,dest_filename,input:CommandData): print(f"**** moving file {orig_filename} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_filename}") - mo_client = mutil.build_mo_client(self._minio_host, self._minio_port,self._minio_access_key, self._minio_secret_key) + mo_client = mutil.build_mo_client(self._s3_host, self._s3_port,self._s3_access_key, self._s3_secret_key) object_name = mutil.mv_file(mo_client,orig_bucket,orig_filename,dest_bucket,dest_filename) if object_name: @@ -101,7 +101,7 @@ def _mv_bucket_object(self,orig_bucket,orig_filename,dest_bucket,dest_filename,i def _cp_bucket_object(self,orig_bucket,orig_filename,dest_bucket,dest_filename,input:CommandData): print(f"**** copying file {orig_filename} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_filename}") - mo_client = mutil.build_mo_client(self._minio_host, self._minio_port,self._minio_access_key, self._minio_secret_key) + mo_client = mutil.build_mo_client(self._s3_host, self._s3_port,self._s3_access_key, self._s3_secret_key) object_name = mutil.cp_file(mo_client,orig_bucket,orig_filename,dest_bucket,dest_filename) if object_name: @@ -121,7 +121,7 @@ def _clean_bucket_content(self,input:CommandData, bucket, pattern=".*", dry_run= """ print(f"**** cleaning bucket {bucket} content matching {pattern}. Dry run mode {dry_run}") - mo_client = mutil.build_mo_client(self._minio_host, self._minio_port,self._minio_access_key, self._minio_secret_key) + mo_client = mutil.build_mo_client(self._s3_host, self._s3_port,self._s3_access_key, self._s3_secret_key) result = [] objects = mo_client.list_objects(bucket_name=bucket, recursive= True) @@ -140,10 +140,10 @@ def _clean_bucket_content(self,input:CommandData, bucket, pattern=".*", dry_run= input.status(200) def execute(self, input:CommandData): - print(f"**** Minio command to execute {input.command()}") + print(f"**** S3 command to execute {input.command()}") try: input.status(400) - input.result(f"could not execute minio command {input.command()} invalid arguments.") + input.result(f"could not execute s3 command {input.command()} invalid arguments.") if "ls" in input.command() and not "args" in input.get_metadata(): self._list_buckets(input) @@ -164,7 +164,7 @@ def execute(self, input:CommandData): self._clean_bucket_content(input,input.args()[0],input.args()[1],input.args()[2]) except Exception as e: - input.result(f"could not execute minio command {e}") + input.result(f"could not execute s3 command {e}") input.status(400) return input \ No newline at end of file diff --git a/actions/devel/minio/common/authorize.py b/actions/devel/s3/common/authorize.py similarity index 100% rename from actions/devel/minio/common/authorize.py rename to actions/devel/s3/common/authorize.py diff --git a/actions/devel/minio/common/command_data.py b/actions/devel/s3/common/command_data.py similarity index 100% rename from actions/devel/minio/common/command_data.py rename to actions/devel/s3/common/command_data.py diff --git a/actions/devel/s3/common/s3_util.py b/actions/devel/s3/common/s3_util.py new file mode 100644 index 00000000..e89f34f4 --- /dev/null +++ b/actions/devel/s3/common/s3_util.py @@ -0,0 +1,182 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import os +import base64 +import random +import string +import mimetypes + +from minio import Minio +from minio.error import S3Error +from minio.commonconfig import CopySource + +def extract_mimetype(file): + mimetype, _ = mimetypes.guess_type(file) + + if mimetype is None: + return "application/octet-stream" + else: + return mimetype + +def build_mo_client(host, port, access_key, secret_key): + """ + Creates an S3 client pointing to the given S3-compatible HOST + :param host, s3 host + :param port, s3 api port normally it is the 9000 + :param access_key user we are representing + :param secret_key to access the object store + """ + mo_client = Minio(f"{host}:{port}",access_key=access_key,secret_key=secret_key,secure=False) + return mo_client + +def upload_file(mo_client, file, bucket, object_name=None): + """Upload a file to an S3 bucket + + :param file_name: File to upload + :param bucket: Bucket to upload to + :param object_name: S3 object name. If not specified then file_name is used + :return: a tuple with the Booleand str, + """ + + # If S3 object_name was not specified, use file_name + if object_name is None: + object_name = os.path.basename(file) + + print(f"uploading {object_name} into bucket {bucket} from tmp_file {file}") + + # Upload the file + try: + mimetype = extract_mimetype(object_name) + response = mo_client.fput_object(bucket_name=bucket,object_name=object_name,file_path=file,content_type=mimetype) + if response._object_name: + return True, response._object_name + except Exception as e: + print(e) + return False, str(e) + return False, "KO" + +def prepare_file_upload(username, filename, file_content_as_b64): + """ Creates a tmp area for the given user where the uploaded file will be stored under a random generated name + the fully qualified filename is taken into account only on the corresponding destination bucket. + param: username + param: filename + param: file_content_as_b64 + return: a file object pointing to the tmp file + """ + try: + user_tmp_folder = f"/tmp/{username}" + if not os.path.exists(user_tmp_folder): + os.makedirs(user_tmp_folder) + print(f"added tmp folder {user_tmp_folder}") + + delete_files_in_directory_and_subdirectories(user_tmp_folder) + rnd_filename = get_random_string(20) + tmp_file = f"{user_tmp_folder}/{rnd_filename}" + + with open(tmp_file, "wb") as f: + file_content=base64.b64decode(file_content_as_b64) + f.write(file_content) + + if os.path.exists(tmp_file): + print(f"{tmp_file} stored successfully.") + return tmp_file + else: + return None + except Exception as e: + print("error preparing tmp_files",e) + return None + +def delete_files_in_directory_and_subdirectories(directory_path): + try: + for root, dirs, files in os.walk(directory_path): + for file in files: + file_path = os.path.join(root, file) + os.remove(file_path) + print("All files and subdirectories deleted successfully.") + except OSError: + print("Error occurred while deleting files and subdirectories.") + +def get_random_string(length): + # choose from all lowercase letter + letters = string.ascii_lowercase + return ''.join(random.choice(letters) for i in range(length)) + +def rm_file(mo_client, bucket, file): + """ Remove a file from a bucket + :parma mo_client a S3 client instance to execute the command + :param bucket the bucket name + :param file the file name + :return True if the file has been removed, False otherwise + """ + + try: + mo_client.remove_object(bucket, file) + return True + except Exception as e: + print(e) + return False + return False + +def cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): + """Copy a file between two buckets + + :param orig_bucket: Origin bucket + :param orig_file: Origin file + :param dest_bucket: Destination bucket + :param dest_file: Destination file + :return: name of the destination file is everything is ok. None otherwise + """ + print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") + try: + # copy an object from a bucket to another. + cp_result = mo_client.copy_object( + dest_bucket, + dest_file, + CopySource(orig_bucket, orig_file) + ) + if cp_result.object_name: + return cp_result.object_name + except Exception as e: + print(e) + return None + return None + +def mv_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): + """Move a file between two buckets + + :param orig_bucket: Origin bucket + :param orig_file: Origin file + :param dest_bucket: Destination bucket + :param dest_file: Destination file + :return: name of the destination file is everything is ok. None otherwise + """ + print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") + + # Upload the file + try: + # mv an object from a bucket to another. + object_name = cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file) + if object_name: + rm_file(mo_client,orig_bucket,orig_file) + return object_name + except Exception as e: + print(e) + return None + return None + diff --git a/actions/devel/minio/common/util.py b/actions/devel/s3/common/util.py similarity index 100% rename from actions/devel/minio/common/util.py rename to actions/devel/s3/common/util.py diff --git a/actions/devel/minio/flatdict.py b/actions/devel/s3/flatdict.py similarity index 100% rename from actions/devel/minio/flatdict.py rename to actions/devel/s3/flatdict.py diff --git a/actions/devel/minio/openserverless/config.py b/actions/devel/s3/openserverless/config.py similarity index 100% rename from actions/devel/minio/openserverless/config.py rename to actions/devel/s3/openserverless/config.py diff --git a/actions/devel/minio/openserverless/couchdb_util.py b/actions/devel/s3/openserverless/couchdb_util.py similarity index 100% rename from actions/devel/minio/openserverless/couchdb_util.py rename to actions/devel/s3/openserverless/couchdb_util.py diff --git a/actions/devel/upload/__main__.py b/actions/devel/upload/__main__.py index e7d00c44..3562c8bd 100644 --- a/actions/devel/upload/__main__.py +++ b/actions/devel/upload/__main__.py @@ -21,7 +21,7 @@ import string import mimetypes -import common.minio_util as mutil +import common.s3_util as mutil import common.util as ut from common.authorize import Authorize @@ -90,7 +90,7 @@ def main(args): """ Action implementing a generic upload wrapper for the nuv devel plugin. The invoker must provide a x-impersonate-auth header containing the Openwhisk BASIC authentication of the wsku/user the action should impersonate when calling this action. The upload action it is supposed to receive a path param similar to // - and will store the given path under the given MINIO . The bucket must exists and the impersonated user must have write permission on it. + and will store the given path under the given S3 . The bucket must exists and the impersonated user must have write permission on it. """ headers = args['__ow_headers'] method = args['__ow_method'] diff --git a/actions/devel/upload/common/minio_util.py b/actions/devel/upload/common/minio_util.py deleted file mode 100644 index e1e8f907..00000000 --- a/actions/devel/upload/common/minio_util.py +++ /dev/null @@ -1,182 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -import os -import base64 -import random -import string -import mimetypes - -from minio import Minio -from minio.error import S3Error -from minio.commonconfig import CopySource - -def extract_mimetype(file): - mimetype, _ = mimetypes.guess_type(file) - - if mimetype is None: - return "application/octet-stream" - else: - return mimetype - -def build_mo_client(host, port, access_key, secret_key): - """ - Creates an Minio client pointing to the given MINIO HOST - :param host, minio host - :param port, minio api port normally it is the 9000 - :param access_key user we are representing - :param secret_key to access minio - """ - mo_client = Minio(f"{host}:{port}",access_key=access_key,secret_key=secret_key,secure=False) - return mo_client - -def upload_file(mo_client, file, bucket, object_name=None): - """Upload a file to an S3 bucket - - :param file_name: File to upload - :param bucket: Bucket to upload to - :param object_name: S3 object name. If not specified then file_name is used - :return: a tuple with the Booleand str, - """ - - # If S3 object_name was not specified, use file_name - if object_name is None: - object_name = os.path.basename(file) - - print(f"uploading {object_name} into bucket {bucket} from tmp_file {file}") - - # Upload the file - try: - mimetype = extract_mimetype(object_name) - response = mo_client.fput_object(bucket_name=bucket,object_name=object_name,file_path=file,content_type=mimetype) - if response._object_name: - return True, response._object_name - except Exception as e: - print(e) - return False, str(e) - return False, "KO" - -def prepare_file_upload(username, filename, file_content_as_b64): - """ Creates a tmp area for the given user where the uploaded file will be stored under a random generated name - the fully qualified filename is taken into account only on the corresponding destination bucket. - param: username - param: filename - param: file_content_as_b64 - return: a file object pointing to the tmp file - """ - try: - user_tmp_folder = f"/tmp/{username}" - if not os.path.exists(user_tmp_folder): - os.makedirs(user_tmp_folder) - print(f"added tmp folder {user_tmp_folder}") - - delete_files_in_directory_and_subdirectories(user_tmp_folder) - rnd_filename = get_random_string(20) - tmp_file = f"{user_tmp_folder}/{rnd_filename}" - - with open(tmp_file, "wb") as f: - file_content=base64.b64decode(file_content_as_b64) - f.write(file_content) - - if os.path.exists(tmp_file): - print(f"{tmp_file} stored successfully.") - return tmp_file - else: - return None - except Exception as e: - print("error preparing tmp_files",e) - return None - -def delete_files_in_directory_and_subdirectories(directory_path): - try: - for root, dirs, files in os.walk(directory_path): - for file in files: - file_path = os.path.join(root, file) - os.remove(file_path) - print("All files and subdirectories deleted successfully.") - except OSError: - print("Error occurred while deleting files and subdirectories.") - -def get_random_string(length): - # choose from all lowercase letter - letters = string.ascii_lowercase - return ''.join(random.choice(letters) for i in range(length)) - -def rm_file(mo_client, bucket, file): - """ Remove a file from a bucket - :parma mo_client a minio client instance to execute the command - :param bucket the bucket name - :param file the file name - :return True if the file has been removed, False otherwise - """ - - try: - mo_client.remove_object(bucket, file) - return True - except Exception as e: - print(e) - return False - return False - -def cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): - """Copy a file between two buckets - - :param orig_bucket: Origin bucket - :param orig_file: Origin file - :param dest_bucket: Destination bucket - :param dest_file: Destination file - :return: name of the destination file is everything is ok. None otherwise - """ - print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") - try: - # copy an object from a bucket to another. - cp_result = mo_client.copy_object( - dest_bucket, - dest_file, - CopySource(orig_bucket, orig_file) - ) - if cp_result.object_name: - return cp_result.object_name - except Exception as e: - print(e) - return None - return None - -def mv_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): - """Move a file between two buckets - - :param orig_bucket: Origin bucket - :param orig_file: Origin file - :param dest_bucket: Destination bucket - :param dest_file: Destination file - :return: name of the destination file is everything is ok. None otherwise - """ - print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") - - # Upload the file - try: - # mv an object from a bucket to another. - object_name = cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file) - if object_name: - rm_file(mo_client,orig_bucket,orig_file) - return object_name - except Exception as e: - print(e) - return None - return None - diff --git a/actions/devel/upload/common/s3_util.py b/actions/devel/upload/common/s3_util.py new file mode 100644 index 00000000..e89f34f4 --- /dev/null +++ b/actions/devel/upload/common/s3_util.py @@ -0,0 +1,182 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import os +import base64 +import random +import string +import mimetypes + +from minio import Minio +from minio.error import S3Error +from minio.commonconfig import CopySource + +def extract_mimetype(file): + mimetype, _ = mimetypes.guess_type(file) + + if mimetype is None: + return "application/octet-stream" + else: + return mimetype + +def build_mo_client(host, port, access_key, secret_key): + """ + Creates an S3 client pointing to the given S3-compatible HOST + :param host, s3 host + :param port, s3 api port normally it is the 9000 + :param access_key user we are representing + :param secret_key to access the object store + """ + mo_client = Minio(f"{host}:{port}",access_key=access_key,secret_key=secret_key,secure=False) + return mo_client + +def upload_file(mo_client, file, bucket, object_name=None): + """Upload a file to an S3 bucket + + :param file_name: File to upload + :param bucket: Bucket to upload to + :param object_name: S3 object name. If not specified then file_name is used + :return: a tuple with the Booleand str, + """ + + # If S3 object_name was not specified, use file_name + if object_name is None: + object_name = os.path.basename(file) + + print(f"uploading {object_name} into bucket {bucket} from tmp_file {file}") + + # Upload the file + try: + mimetype = extract_mimetype(object_name) + response = mo_client.fput_object(bucket_name=bucket,object_name=object_name,file_path=file,content_type=mimetype) + if response._object_name: + return True, response._object_name + except Exception as e: + print(e) + return False, str(e) + return False, "KO" + +def prepare_file_upload(username, filename, file_content_as_b64): + """ Creates a tmp area for the given user where the uploaded file will be stored under a random generated name + the fully qualified filename is taken into account only on the corresponding destination bucket. + param: username + param: filename + param: file_content_as_b64 + return: a file object pointing to the tmp file + """ + try: + user_tmp_folder = f"/tmp/{username}" + if not os.path.exists(user_tmp_folder): + os.makedirs(user_tmp_folder) + print(f"added tmp folder {user_tmp_folder}") + + delete_files_in_directory_and_subdirectories(user_tmp_folder) + rnd_filename = get_random_string(20) + tmp_file = f"{user_tmp_folder}/{rnd_filename}" + + with open(tmp_file, "wb") as f: + file_content=base64.b64decode(file_content_as_b64) + f.write(file_content) + + if os.path.exists(tmp_file): + print(f"{tmp_file} stored successfully.") + return tmp_file + else: + return None + except Exception as e: + print("error preparing tmp_files",e) + return None + +def delete_files_in_directory_and_subdirectories(directory_path): + try: + for root, dirs, files in os.walk(directory_path): + for file in files: + file_path = os.path.join(root, file) + os.remove(file_path) + print("All files and subdirectories deleted successfully.") + except OSError: + print("Error occurred while deleting files and subdirectories.") + +def get_random_string(length): + # choose from all lowercase letter + letters = string.ascii_lowercase + return ''.join(random.choice(letters) for i in range(length)) + +def rm_file(mo_client, bucket, file): + """ Remove a file from a bucket + :parma mo_client a S3 client instance to execute the command + :param bucket the bucket name + :param file the file name + :return True if the file has been removed, False otherwise + """ + + try: + mo_client.remove_object(bucket, file) + return True + except Exception as e: + print(e) + return False + return False + +def cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): + """Copy a file between two buckets + + :param orig_bucket: Origin bucket + :param orig_file: Origin file + :param dest_bucket: Destination bucket + :param dest_file: Destination file + :return: name of the destination file is everything is ok. None otherwise + """ + print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") + try: + # copy an object from a bucket to another. + cp_result = mo_client.copy_object( + dest_bucket, + dest_file, + CopySource(orig_bucket, orig_file) + ) + if cp_result.object_name: + return cp_result.object_name + except Exception as e: + print(e) + return None + return None + +def mv_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file): + """Move a file between two buckets + + :param orig_bucket: Origin bucket + :param orig_file: Origin file + :param dest_bucket: Destination bucket + :param dest_file: Destination file + :return: name of the destination file is everything is ok. None otherwise + """ + print(f"**** moving file {orig_file} from bucket {orig_bucket} to bucket {dest_bucket} with name {dest_file}") + + # Upload the file + try: + # mv an object from a bucket to another. + object_name = cp_file(mo_client, orig_bucket, orig_file, dest_bucket, dest_file) + if object_name: + rm_file(mo_client,orig_bucket,orig_file) + return object_name + except Exception as e: + print(e) + return None + return None + diff --git a/actions/github/callback/__main__.py b/actions/github/callback/__main__.py index e5789f10..2956b7b6 100644 --- a/actions/github/callback/__main__.py +++ b/actions/github/callback/__main__.py @@ -50,7 +50,7 @@ def main(args): """ Action implementing the callback activated when a user subscribe to the nuv-github-app. This implementation will use the code parameter to retrieve access_token and refresh_token representing the user - and will create a openserverless user having an openwhisk namespace and minio bucket with 100m quota. + and will create a openserverless user having an openwhisk namespace and s3 bucket with 100m quota. """ print(args) diff --git a/actions/github/common/whisk_user_generator.py b/actions/github/common/whisk_user_generator.py index 8bf28de1..9f102e67 100644 --- a/actions/github/common/whisk_user_generator.py +++ b/actions/github/common/whisk_user_generator.py @@ -37,10 +37,10 @@ def pwd_generator(): def generate_whisk_user_yaml(username,email): """ - Return a dictionary Object representing a whisk-user.yaml to create a user wiht OW namespace and MINIO buckets + Return a dictionary Object representing a whisk-user.yaml to create a user wiht OW namespace and S3 buckets """ password = pwd_generator() - minio_secret = auth_generator() + s3_secret = auth_generator() auth = auth_generator() return { @@ -56,7 +56,7 @@ def generate_whisk_user_yaml(username,email): "namespace": username, "auth": auth, "object-storage": { - "password": minio_secret, + "password": s3_secret, "quota": "100", "data":{ "enabled": True, diff --git a/actions/system.yml b/actions/system.yml index 4da7a250..39ee7465 100644 --- a/actions/system.yml +++ b/actions/system.yml @@ -34,7 +34,7 @@ tasks: content:prepare: - |- mkdir -p content/common - cp ./common/minio_util.py content/common + cp ./common/s3_util.py content/common - |- cd content rm -f ../{{.DEPLOY}}/content.zip @@ -64,17 +64,17 @@ tasks: rm -f ../../{{.DEPLOY}}/psql.zip zip -r ../../{{.DEPLOY}}/psql.zip * - minio:prepare: + s3:prepare: - |- - mkdir -p devel/minio/openserverless - cp ../openserverless/config.py ../openserverless/couchdb_util.py devel/minio/openserverless + mkdir -p devel/s3/openserverless + cp ../openserverless/config.py ../openserverless/couchdb_util.py devel/s3/openserverless - |- - mkdir -p devel/minio/common - cp ./common/*.py devel/minio/common + mkdir -p devel/s3/common + cp ./common/*.py devel/s3/common - |- - cd devel/minio - rm -f ../../{{.DEPLOY}}/minio.zip - zip -r ../../{{.DEPLOY}}/minio.zip * + cd devel/s3 + rm -f ../../{{.DEPLOY}}/s3.zip + zip -r ../../{{.DEPLOY}}/s3.zip * devel_upload:prepare: - |- @@ -128,7 +128,7 @@ tasks: - task: content:prepare - task: redis:prepare - task: psql:prepare - - task: minio:prepare + - task: s3:prepare - task: devel_upload:prepare - task: devel_download:prepare - task: ferretdb:prepare diff --git a/demos/devel/minio_buckets.json b/demos/devel/s3_buckets.json similarity index 100% rename from demos/devel/minio_buckets.json rename to demos/devel/s3_buckets.json diff --git a/demos/devel/minio_cp.json b/demos/devel/s3_cp.json similarity index 100% rename from demos/devel/minio_cp.json rename to demos/devel/s3_cp.json diff --git a/demos/devel/minio_ls.json b/demos/devel/s3_ls.json similarity index 100% rename from demos/devel/minio_ls.json rename to demos/devel/s3_ls.json diff --git a/demos/devel/minio_mv.json b/demos/devel/s3_mv.json similarity index 100% rename from demos/devel/minio_mv.json rename to demos/devel/s3_mv.json diff --git a/demos/devel/minio_rm.json b/demos/devel/s3_rm.json similarity index 100% rename from demos/devel/minio_rm.json rename to demos/devel/s3_rm.json diff --git a/demos/milvus/milvus_admin_test.py b/demos/milvus/milvus_admin_test.py index 598d53fc..850d82e8 100644 --- a/demos/milvus/milvus_admin_test.py +++ b/demos/milvus/milvus_admin_test.py @@ -20,7 +20,7 @@ import os from openserverless.milvus_simple_client import MilvusSimpleClient as MilvusClient -# for this test minioClient should see this env variable +# for this test Milvus should see this env variable os.environ['MILVUS_ROOT_USER']='root' os.environ['MILVUS_API_HOST']='localhost' os.environ['MILVUS_API_PORT']='19530' diff --git a/deploy/content/index.html b/deploy/content/index.html index 10e24b39..cfdd136a 100644 --- a/deploy/content/index.html +++ b/deploy/content/index.html @@ -19,14 +19,52 @@ - + - Apache OpenServerless (incubating) Static Content landing page + + Welcome to Apache OpenServerless + -

- Welcome to Apache OpenServerless (incubating) static content distributor landing page!!! -

+ +

Welcome to Apache OpenServerless

+

Static content distributor landing page

- \ No newline at end of file + diff --git a/deploy/demos/devel/minio_buckets.json b/deploy/demos/devel/minio_buckets.json deleted file mode 100644 index d548f6b5..00000000 --- a/deploy/demos/devel/minio_buckets.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "command":"ls" -} \ No newline at end of file diff --git a/deploy/demos/devel/minio_cp.json b/deploy/demos/devel/minio_cp.json deleted file mode 100644 index a394bd6c..00000000 --- a/deploy/demos/devel/minio_cp.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "command":"cp", - "args":[ - "openserverless-web", - "index.html", - "openserverless-web", - "tidy/content2.html" - ] -} \ No newline at end of file diff --git a/deploy/demos/devel/minio_ls.json b/deploy/demos/devel/minio_ls.json deleted file mode 100644 index 93c016e7..00000000 --- a/deploy/demos/devel/minio_ls.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "command":"ls", - "args":[ - "openserverless-web" - ] -} \ No newline at end of file diff --git a/deploy/demos/devel/minio_mv.json b/deploy/demos/devel/minio_mv.json deleted file mode 100644 index 061d80c0..00000000 --- a/deploy/demos/devel/minio_mv.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "command":"mv", - "args":[ - "openserverless-web", - "content.html", - "openserverless-web", - "tidy/content.html" - ] -} \ No newline at end of file diff --git a/deploy/demos/devel/minio_rm.json b/deploy/demos/devel/minio_rm.json deleted file mode 100644 index 80eab9cb..00000000 --- a/deploy/demos/devel/minio_rm.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "command":"rm", - "args":[ - "openserverless-web", - "tidy/content.html" - ] -} \ No newline at end of file diff --git a/deploy/demos/reference/milvus_manifest.yaml b/deploy/demos/reference/milvus_manifest.yaml deleted file mode 100644 index 9f4b8f10..00000000 --- a/deploy/demos/reference/milvus_manifest.yaml +++ /dev/null @@ -1,3073 +0,0 @@ -# Source: milvus/charts/pulsarv3/templates/autorecovery-service-account.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/minio/templates/serviceaccount.yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - name: "openserverless-milvus-minio" - namespace: "openserverless" - labels: - app: minio - chart: minio-8.0.17 - release: "openserverless-milvus" ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: "openserverless-milvus-pulsarv3-recovery" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: recovery - annotations: ---- -# Source: milvus/charts/pulsarv3/templates/bookkeeper-service-account.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: v1 -kind: ServiceAccount -metadata: - name: "openserverless-milvus-pulsarv3-bookie" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: bookie - annotations: ---- -# Source: milvus/charts/pulsarv3/templates/broker-service-account.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: v1 -kind: ServiceAccount -metadata: - name: "openserverless-milvus-pulsarv3-broker-acct" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: broker - annotations: ---- -# Source: milvus/charts/pulsarv3/templates/proxy-service-account.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: v1 -kind: ServiceAccount -metadata: - name: "openserverless-milvus-pulsarv3-proxy" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: proxy - annotations: ---- -# Source: milvus/charts/pulsarv3/templates/zookeeper-service-account.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: v1 -kind: ServiceAccount -metadata: - name: "openserverless-milvus-pulsarv3-zookeeper" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: zookeeper - annotations: ---- -# Source: milvus/charts/minio/templates/secrets.yaml -apiVersion: v1 -kind: Secret -metadata: - name: openserverless-milvus-minio - labels: - app: minio - chart: minio-8.0.17 - release: openserverless-milvus - heritage: Helm -type: Opaque -data: - accesskey: "bWluaW9hZG1pbg==" - secretkey: "bWluaW9hZG1pbg==" ---- -# Source: milvus/charts/minio/templates/configmap.yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: openserverless-milvus-minio - labels: - app: minio - chart: minio-8.0.17 - release: openserverless-milvus - heritage: Helm -data: - initialize: |- - #!/bin/sh -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - - set -e ; # Have script exit in the event of a failed command. - MC_CONFIG_DIR="/etc/minio/mc/" - MC="/usr/bin/mc --insecure --config-dir ${MC_CONFIG_DIR}" - - # connectToMinio - # Use a check-sleep-check loop to wait for Minio service to be available - connectToMinio() { - SCHEME=$1 - ATTEMPTS=0 ; LIMIT=29 ; # Allow 30 attempts - set -e ; # fail if we can't read the keys. - ACCESS=$(cat /config/accesskey) ; SECRET=$(cat /config/secretkey) ; - set +e ; # The connections to minio are allowed to fail. - echo "Connecting to Minio server: $SCHEME://$MINIO_ENDPOINT:$MINIO_PORT" ; - MC_COMMAND="${MC} config host add myminio $SCHEME://$MINIO_ENDPOINT:$MINIO_PORT $ACCESS $SECRET" ; - $MC_COMMAND ; - STATUS=$? ; - until [ $STATUS = 0 ] - do - ATTEMPTS=`expr $ATTEMPTS + 1` ; - echo \"Failed attempts: $ATTEMPTS\" ; - if [ $ATTEMPTS -gt $LIMIT ]; then - exit 1 ; - fi ; - sleep 2 ; # 1 second intervals between attempts - $MC_COMMAND ; - STATUS=$? ; - done ; - set -e ; # reset `e` as active - return 0 - } - - # checkBucketExists ($bucket) - # Check if the bucket exists, by using the exit code of `mc ls` - checkBucketExists() { - BUCKET=$1 - CMD=$(${MC} ls myminio/$BUCKET > /dev/null 2>&1) - return $? - } - - # createBucket ($bucket, $policy, $purge) - # Ensure bucket exists, purging if asked to - createBucket() { - BUCKET=$1 - POLICY=$2 - PURGE=$3 - VERSIONING=$4 - - # Purge the bucket, if set & exists - # Since PURGE is user input, check explicitly for `true` - if [ $PURGE = true ]; then - if checkBucketExists $BUCKET ; then - echo "Purging bucket '$BUCKET'." - set +e ; # don't exit if this fails - ${MC} rm -r --force myminio/$BUCKET - set -e ; # reset `e` as active - else - echo "Bucket '$BUCKET' does not exist, skipping purge." - fi - fi - - # Create the bucket if it does not exist - if ! checkBucketExists $BUCKET ; then - echo "Creating bucket '$BUCKET'" - ${MC} mb myminio/$BUCKET - else - echo "Bucket '$BUCKET' already exists." - fi - - - # set versioning for bucket - if [ ! -z $VERSIONING ] ; then - if [ $VERSIONING = true ] ; then - echo "Enabling versioning for '$BUCKET'" - ${MC} version enable myminio/$BUCKET - elif [ $VERSIONING = false ] ; then - echo "Suspending versioning for '$BUCKET'" - ${MC} version suspend myminio/$BUCKET - fi - else - echo "Bucket '$BUCKET' versioning unchanged." - fi - - # At this point, the bucket should exist, skip checking for existence - # Set policy on the bucket - echo "Setting policy of bucket '$BUCKET' to '$POLICY'." - ${MC} policy set $POLICY myminio/$BUCKET - } - - # Try connecting to Minio instance - scheme=http - connectToMinio $scheme ---- -# Source: milvus/charts/pulsarv3/templates/autorecovery-configmap.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: v1 -kind: ConfigMap -metadata: - name: "openserverless-milvus-pulsarv3-recovery" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: recovery -data: - # common config - zkServers: "openserverless-milvus-pulsarv3-zookeeper:2181" - zkLedgersRootPath: "/ledgers" - # enable bookkeeper http server - httpServerEnabled: "true" - httpServerPort: "8000" - # config the stats provider - statsProviderClass: org.apache.bookkeeper.stats.prometheus.PrometheusMetricsProvider - # use hostname as the bookie id - useHostNameAsBookieID: "true" - BOOKIE_MEM: | - -Xms128m -Xmx128m - PULSAR_PREFIX_useV2WireProtocol: "true" ---- -# Source: milvus/charts/pulsarv3/templates/bookkeeper-configmap.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: v1 -kind: ConfigMap -metadata: - name: "openserverless-milvus-pulsarv3-bookie" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: bookie -data: - # common config - zkServers: "openserverless-milvus-pulsarv3-zookeeper:2181" - zkLedgersRootPath: "/ledgers" - # enable bookkeeper http server - httpServerEnabled: "true" - httpServerPort: "8000" - # config the stats provider - statsProviderClass: org.apache.bookkeeper.stats.prometheus.PrometheusMetricsProvider - # use hostname as the bookie id - useHostNameAsBookieID: "true" - # disable auto recovery on bookies since we will start AutoRecovery in separated pods - autoRecoveryDaemonEnabled: "false" - # Do not retain journal files as it increase the disk utilization - journalMaxBackups: "0" - journalDirectories: "/pulsar/data/bookkeeper/journal" - PULSAR_PREFIX_journalDirectories: "/pulsar/data/bookkeeper/journal" - ledgerDirectories: "/pulsar/data/bookkeeper/ledgers" - # TLS config - - PULSAR_GC: | - -XX:+UseG1GC -XX:MaxGCPauseMillis=10 -XX:+ParallelRefProcEnabled -XX:+UnlockExperimentalVMOptions -XX:+DoEscapeAnalysis -XX:ParallelGCThreads=4 -XX:ConcGCThreads=4 -XX:G1NewSizePercent=50 -XX:+DisableExplicitGC -XX:-ResizePLAB -XX:+ExitOnOutOfMemoryError -XX:+PerfDisableSharedMem - PULSAR_MEM: | - -Xms4096m -Xmx4096m -XX:MaxDirectMemorySize=8192m - diskCheckInterval: "1800" - diskUsageLwmThreshold: "0.85" - diskUsageThreshold: "0.95" - diskUsageWarnThreshold: "0.9" - gcWaitTime: "300000" - isForceGCAllowWhenNoSpace: "true" - majorCompactionInterval: "10800" - majorCompactionThreshold: "0.8" - minorCompactionInterval: "360" - minorCompactionThreshold: "0.2" - nettyMaxFrameSizeBytes: "104867840" ---- -# Source: milvus/charts/pulsarv3/templates/broker-configmap.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: v1 -kind: ConfigMap -metadata: - name: "openserverless-milvus-pulsarv3-broker" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: broker -data: - # Metadata settings - zookeeperServers: "openserverless-milvus-pulsarv3-zookeeper:2181" - configurationStoreServers: "openserverless-milvus-pulsarv3-zookeeper:2181" - - # Broker settings - clusterName: openserverless-milvus-pulsarv3 - exposeTopicLevelMetricsInPrometheus: "true" - numHttpServerThreads: "8" - zooKeeperSessionTimeoutMillis: "30000" - statusFilePath: "/pulsar/status" - - # Tiered storage settings - - # Function Worker Settings - # function worker configuration - functionsWorkerEnabled: "false" - - # prometheus needs to access /metrics endpoint - webServicePort: "8080" - brokerServicePort: "6650" - - # Authentication Settings - PULSAR_GC: | - -XX:+UseG1GC -XX:MaxGCPauseMillis=10 -Dio.netty.leakDetectionLevel=disabled -Dio.netty.recycler.linkCapacity=1024 -XX:+ParallelRefProcEnabled -XX:+UnlockExperimentalVMOptions -XX:+DoEscapeAnalysis -XX:ParallelGCThreads=4 -XX:ConcGCThreads=4 -XX:G1NewSizePercent=50 -XX:+DisableExplicitGC -XX:-ResizePLAB -XX:+ExitOnOutOfMemoryError -XX:+PerfDisableSharedMem - PULSAR_MEM: | - -Xms4096m -Xmx4096m -XX:MaxDirectMemorySize=8192m - backlogQuotaDefaultLimitGB: "8" - backlogQuotaDefaultRetentionPolicy: producer_exception - defaultRetentionSizeInMB: "-1" - defaultRetentionTimeInMinutes: "10080" - managedLedgerDefaultAckQuorum: "2" - managedLedgerDefaultEnsembleSize: "3" - managedLedgerDefaultWriteQuorum: "3" - maxMessageSize: "104857600" - subscriptionExpirationTimeMinutes: "3" - ttlDurationDefaultInSeconds: "259200" ---- -# Source: milvus/charts/pulsarv3/templates/proxy-configmap.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: v1 -kind: ConfigMap -metadata: - name: "openserverless-milvus-pulsarv3-proxy" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: proxy -data: - clusterName: openserverless-milvus-pulsarv3 - statusFilePath: "/pulsar/status" - # prometheus needs to access /metrics endpoint - webServicePort: "8080" - servicePort: "6650" - brokerServiceURL: pulsar://openserverless-milvus-pulsarv3-broker:6650 - brokerWebServiceURL: http://openserverless-milvus-pulsarv3-broker:8080 - - # Authentication Settings - PULSAR_GC: | - -XX:+UseG1GC -XX:MaxGCPauseMillis=10 -Dio.netty.leakDetectionLevel=disabled -Dio.netty.recycler.linkCapacity=1024 -XX:+ParallelRefProcEnabled -XX:+UnlockExperimentalVMOptions -XX:+DoEscapeAnalysis -XX:ParallelGCThreads=4 -XX:ConcGCThreads=4 -XX:G1NewSizePercent=50 -XX:+DisableExplicitGC -XX:-ResizePLAB -XX:+ExitOnOutOfMemoryError -XX:+PerfDisableSharedMem - PULSAR_MEM: | - -Xms512m -Xmx512m -XX:MaxDirectMemorySize=2048m - httpNumThreads: "8" ---- -# Source: milvus/charts/pulsarv3/templates/zookeeper-configmap.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# deploy zookeeper only when `components.zookeeper` is true -apiVersion: v1 -kind: ConfigMap -metadata: - name: "openserverless-milvus-pulsarv3-zookeeper" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: zookeeper -data: - dataDir: /pulsar/data/zookeeper - PULSAR_PREFIX_serverCnxnFactory: org.apache.zookeeper.server.NIOServerCnxnFactory - serverCnxnFactory: org.apache.zookeeper.server.NIOServerCnxnFactory - PULSAR_GC: | - -XX:+UseG1GC -XX:MaxGCPauseMillis=10 -Dcom.sun.management.jmxremote -Djute.maxbuffer=10485760 -XX:+ParallelRefProcEnabled -XX:+UnlockExperimentalVMOptions -XX:+DoEscapeAnalysis -XX:+DisableExplicitGC -XX:+ExitOnOutOfMemoryError -XX:+PerfDisableSharedMem - PULSAR_MEM: | - -Xms256m -Xmx256m ---- -# Source: milvus/templates/configmap.yaml -# If customConfigMap is not set, this ConfigMap will be redendered. -apiVersion: v1 -kind: ConfigMap -metadata: - name: openserverless-milvus - namespace: openserverless -data: - default.yaml: |+ - # Copyright (C) 2019-2021 Zilliz. All rights reserved. - # - # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance - # with the License. You may obtain a copy of the License at - # - # http://www.apache.org/licenses/LICENSE-2.0 - # - # Unless required by applicable law or agreed to in writing, software distributed under the License - # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - # or implied. See the License for the specific language governing permissions and limitations under the License. - - etcd: - endpoints: - - openserverless-milvus-etcd-0.openserverless-milvus-etcd-headless.openserverless.svc.cluster.local:2379 - - metastore: - type: etcd - - minio: - address: openserverless-milvus-minio - port: 9000 - accessKeyID: minioadmin - secretAccessKey: minioadmin - useSSL: false - bucketName: milvus-bucket - rootPath: file - useIAM: false - useVirtualHost: false - - mq: - type: pulsar - - messageQueue: pulsar - - pulsar: - address: openserverless-milvus-pulsarv3-proxy - port: 6650 - maxMessageSize: 104857600 - - mq: - type: rocksmq - - messageQueue: rocksmq - - rootCoord: - address: localhost - port: 53100 - enableActiveStandby: false # Enable rootcoord active-standby - - proxy: - port: 19530 - internalPort: 19529 - - queryCoord: - address: localhost - port: 19531 - - enableActiveStandby: false # Enable querycoord active-standby - - queryNode: - port: 21123 - enableDisk: true # Enable querynode load disk index, and search on disk index - - indexCoord: - address: localhost - port: 31000 - enableActiveStandby: false # Enable indexcoord active-standby - - indexNode: - port: 21121 - enableDisk: true # Enable index node build disk vector index - - dataCoord: - address: localhost - port: 13333 - enableActiveStandby: false # Enable datacoord active-standby - - dataNode: - port: 21124 - - log: - level: info - file: - rootPath: "" - maxSize: 300 - maxAge: 10 - maxBackups: 20 - format: text - user.yaml: |- - # For example enable rest http for milvus proxy - # proxy: - # http: - # enabled: true - # maxUserNum: 100 - # maxRoleNum: 10 - ## Enable tlsMode and set the tls cert and key - # tls: - # serverPemPath: /etc/milvus/certs/tls.crt - # serverKeyPath: /etc/milvus/certs/tls.key - # common: - # security: - # tlsMode: 1 ---- -# Source: milvus/charts/minio/templates/pvc.yaml -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: openserverless-milvus-minio - annotations: - helm.sh/resource-policy: keep - labels: - app: minio - chart: minio-8.0.17 - release: openserverless-milvus - heritage: Helm -spec: - accessModes: - - "ReadWriteOnce" - resources: - requests: - storage: "500Gi" ---- -# Source: milvus/templates/pvc.yaml -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: openserverless-milvus - namespace: openserverless - annotations: - helm.sh/resource-policy: keep - labels: - helm.sh/chart: milvus-4.2.32 - app.kubernetes.io/name: milvus - app.kubernetes.io/instance: openserverless-milvus - app.kubernetes.io/version: "2.5.0-beta" - app.kubernetes.io/managed-by: Helm -spec: - accessModes: - - "ReadWriteOnce" - resources: - requests: - storage: 50Gi ---- -# Source: milvus/charts/pulsarv3/templates/broker-cluster-role-binding.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: "openserverless-milvus-pulsarv3-broker-role" - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 -rules: -- apiGroups: [""] - resources: - - configmaps - verbs: ["get", "list", "watch"] -- apiGroups: ["", "extensions", "apps"] - resources: - - pods - - services - - deployments - - secrets - - statefulsets - verbs: - - list - - watch - - get - - update - - create - - delete - - patch ---- -# Source: milvus/charts/pulsarv3/templates/broker-cluster-role-binding.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -## TODO create our own cluster role with less privledges than admin -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: "openserverless-milvus-pulsarv3-broker-rolebinding" - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: "openserverless-milvus-pulsarv3-broker-role" -subjects: -- kind: ServiceAccount - name: "openserverless-milvus-pulsarv3-broker-acct" - namespace: openserverless ---- -# Source: milvus/charts/etcd/templates/svc-headless.yaml -apiVersion: v1 -kind: Service -metadata: - name: openserverless-milvus-etcd-headless - namespace: openserverless - labels: - app.kubernetes.io/name: etcd - helm.sh/chart: etcd-6.3.3 - app.kubernetes.io/instance: openserverless-milvus - app.kubernetes.io/managed-by: Helm - annotations: - service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" -spec: - type: ClusterIP - clusterIP: None - publishNotReadyAddresses: true - ports: - - name: "client" - port: 2379 - targetPort: client - - name: "peer" - port: 2380 - targetPort: peer - selector: - app.kubernetes.io/name: etcd - app.kubernetes.io/instance: openserverless-milvus ---- -# Source: milvus/charts/etcd/templates/svc.yaml -apiVersion: v1 -kind: Service -metadata: - name: openserverless-milvus-etcd - namespace: openserverless - labels: - app.kubernetes.io/name: etcd - helm.sh/chart: etcd-6.3.3 - app.kubernetes.io/instance: openserverless-milvus - app.kubernetes.io/managed-by: Helm - annotations: -spec: - type: ClusterIP - ports: - - name: "client" - port: 2379 - targetPort: client - nodePort: null - - name: "peer" - port: 2380 - targetPort: peer - nodePort: null - selector: - app.kubernetes.io/name: etcd - app.kubernetes.io/instance: openserverless-milvus ---- -# Source: milvus/charts/minio/templates/service.yaml -apiVersion: v1 -kind: Service -metadata: - name: openserverless-milvus-minio - labels: - app: minio - chart: minio-8.0.17 - release: openserverless-milvus - heritage: Helm -spec: - type: ClusterIP - ports: - - name: http - port: 9000 - protocol: TCP - targetPort: 9000 - selector: - app: minio - release: openserverless-milvus ---- -# Source: milvus/charts/pulsarv3/templates/autorecovery-service.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: v1 -kind: Service -metadata: - name: "openserverless-milvus-pulsarv3-recovery" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: recovery -spec: - ports: - - name: http - port: 8000 - clusterIP: None - selector: - app: pulsarv3 - release: openserverless-milvus - component: recovery ---- -# Source: milvus/charts/pulsarv3/templates/bookkeeper-service.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: v1 -kind: Service -metadata: - name: "openserverless-milvus-pulsarv3-bookie" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: bookie -spec: - ports: - - name: "bookie" - port: 3181 - - name: http - port: 8000 - clusterIP: None - selector: - app: pulsarv3 - release: openserverless-milvus - component: bookie - publishNotReadyAddresses: true ---- -# Source: milvus/charts/pulsarv3/templates/broker-service.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: v1 -kind: Service -metadata: - name: "openserverless-milvus-pulsarv3-broker" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: broker - annotations: - {} -spec: - ports: - # prometheus needs to access /metrics endpoint - - name: http - port: 8080 - - name: "pulsar" - port: 6650 - clusterIP: None - selector: - app: pulsarv3 - release: openserverless-milvus - component: broker ---- -# Source: milvus/charts/pulsarv3/templates/proxy-service.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: v1 -kind: Service -metadata: - name: "openserverless-milvus-pulsarv3-proxy" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: proxy - annotations: -spec: - type: ClusterIP - ports: - - name: http - port: 80 - protocol: TCP - targetPort: sts-http - - name: "pulsar" - port: 6650 - protocol: TCP - targetPort: "sts-pulsar" - selector: - app: pulsarv3 - release: openserverless-milvus - component: proxy ---- -# Source: milvus/charts/pulsarv3/templates/zookeeper-service.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# deploy zookeeper only when `components.zookeeper` is true -apiVersion: v1 -kind: Service -metadata: - name: "openserverless-milvus-pulsarv3-zookeeper" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: zookeeper - annotations: - {} -spec: - ports: - # prometheus needs to access /metrics endpoint - - name: http - port: 8000 - - name: "follower" - port: 2888 - - name: "leader-election" - port: 3888 - - name: "client" - port: 2181 - clusterIP: None - publishNotReadyAddresses: true - selector: - app: pulsarv3 - release: openserverless-milvus - component: zookeeper ---- -# Source: milvus/templates/service.yaml -apiVersion: v1 -kind: Service -metadata: - name: openserverless-milvus - namespace: openserverless - labels: - helm.sh/chart: milvus-4.2.32 - app.kubernetes.io/name: milvus - app.kubernetes.io/instance: openserverless-milvus - app.kubernetes.io/version: "2.5.0-beta" - app.kubernetes.io/managed-by: Helm - component: "standalone" -spec: - type: ClusterIP - ports: - - name: milvus - port: 19530 - protocol: TCP - targetPort: milvus - - name: metrics - protocol: TCP - port: 9091 - targetPort: metrics - selector: - app.kubernetes.io/name: milvus - app.kubernetes.io/instance: openserverless-milvus - component: "standalone" ---- -# Source: milvus/charts/minio/templates/deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: openserverless-milvus-minio - labels: - app: minio - chart: minio-8.0.17 - release: openserverless-milvus - heritage: Helm -spec: - strategy: - type: RollingUpdate - rollingUpdate: - maxSurge: 100% - maxUnavailable: 0 - selector: - matchLabels: - app: minio - release: openserverless-milvus - template: - metadata: - name: openserverless-milvus-minio - labels: - app: minio - release: openserverless-milvus - annotations: - checksum/secrets: ff1e94b7d737d105c2dc9d21647326f2431dcb35e2592f6d63feaddb4f2bea82 - checksum/config: dcce09c6c2c8f2bccb379c721fa4dad7b9d4cae29d4977bbc6caadd1d1cefcf2 - spec: - serviceAccountName: "openserverless-milvus-minio" - securityContext: - runAsUser: 1000 - runAsGroup: 1000 - fsGroup: 1000 - containers: - - name: minio - image: "minio/minio:RELEASE.2023-03-20T20-16-18Z" - imagePullPolicy: IfNotPresent - command: - - "/bin/sh" - - "-ce" - - "/usr/bin/docker-entrypoint.sh minio -S /etc/minio/certs/ server /export" - volumeMounts: - - name: export - mountPath: /export - ports: - - name: http - containerPort: 9000 - livenessProbe: - httpGet: - path: /minio/health/live - port: http - scheme: HTTP - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - tcpSocket: - port: http - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 5 - startupProbe: - tcpSocket: - port: http - initialDelaySeconds: 0 - periodSeconds: 10 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 60 - env: - - name: MINIO_ACCESS_KEY - valueFrom: - secretKeyRef: - name: openserverless-milvus-minio - key: accesskey - - name: MINIO_SECRET_KEY - valueFrom: - secretKeyRef: - name: openserverless-milvus-minio - key: secretkey - resources: - requests: - memory: 2Gi - volumes: - - name: export - persistentVolumeClaim: - claimName: openserverless-milvus-minio - - name: minio-user - secret: - secretName: openserverless-milvus-minio ---- -# Source: milvus/templates/standalone-deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: openserverless-milvus-standalone - namespace: openserverless - labels: - helm.sh/chart: milvus-4.2.32 - app.kubernetes.io/name: milvus - app.kubernetes.io/instance: openserverless-milvus - app.kubernetes.io/version: "2.5.0-beta" - app.kubernetes.io/managed-by: Helm - component: "standalone" - - annotations: - - -spec: - replicas: 1 - strategy: - type: Recreate - selector: - matchLabels: - app.kubernetes.io/name: milvus - app.kubernetes.io/instance: openserverless-milvus - component: "standalone" - template: - metadata: - labels: - app.kubernetes.io/name: milvus - app.kubernetes.io/instance: openserverless-milvus - component: "standalone" - - annotations: - checksum/config: 56521368c7f257654c000e06d0f63b7617ad04ac0dacce068a188b86a418c197 - - spec: - serviceAccountName: default - initContainers: - containers: - - name: standalone - image: "milvusdb/milvus:v2.5.0-beta" - imagePullPolicy: IfNotPresent - args: [ "milvus", "run", "standalone" ] - ports: - - name: milvus - containerPort: 19530 - protocol: TCP - - name: metrics - containerPort: 9091 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: metrics - initialDelaySeconds: 90 - periodSeconds: 30 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - httpGet: - path: /healthz - port: metrics - initialDelaySeconds: 90 - periodSeconds: 10 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 5 - resources: - {} - env: - volumeMounts: - - mountPath: /milvus/tools - name: tools - - name: milvus-config - mountPath: /milvus/configs/default.yaml - subPath: default.yaml - readOnly: true - - name: milvus-config - mountPath: /milvus/configs/user.yaml - subPath: user.yaml - readOnly: true - - name: milvus-data-disk - mountPath: "/var/lib/milvus" - subPath: - - mountPath: /var/lib/milvus/data - name: disk - - volumes: - - emptyDir: {} - name: tools - - name: milvus-config - configMap: - name: openserverless-milvus - - name: milvus-data-disk - persistentVolumeClaim: - claimName: openserverless-milvus - - name: disk - emptyDir: {} ---- -# Source: milvus/charts/etcd/templates/statefulset.yaml -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: openserverless-milvus-etcd - namespace: openserverless - labels: - app.kubernetes.io/name: etcd - helm.sh/chart: etcd-6.3.3 - app.kubernetes.io/instance: openserverless-milvus - app.kubernetes.io/managed-by: Helm -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: etcd - app.kubernetes.io/instance: openserverless-milvus - serviceName: openserverless-milvus-etcd-headless - podManagementPolicy: Parallel - updateStrategy: - type: RollingUpdate - template: - metadata: - labels: - app.kubernetes.io/name: etcd - helm.sh/chart: etcd-6.3.3 - app.kubernetes.io/instance: openserverless-milvus - app.kubernetes.io/managed-by: Helm - annotations: - spec: - - affinity: - podAffinity: - - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - podAffinityTerm: - labelSelector: - matchLabels: - app.kubernetes.io/name: etcd - app.kubernetes.io/instance: openserverless-milvus - namespaces: - - "openserverless" - topologyKey: kubernetes.io/hostname - weight: 1 - nodeAffinity: - - securityContext: - fsGroup: 1001 - serviceAccountName: "default" - containers: - - name: etcd - image: docker.io/bitnamilegacy/etcd:3.5.17-debian-12-r0 - imagePullPolicy: "IfNotPresent" - securityContext: - runAsNonRoot: true - runAsUser: 1001 - env: - - name: BITNAMI_DEBUG - value: "false" - - name: MY_POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: ETCDCTL_API - value: "3" - - name: ETCD_ON_K8S - value: "yes" - - name: ETCD_START_FROM_SNAPSHOT - value: "no" - - name: ETCD_DISASTER_RECOVERY - value: "no" - - name: ETCD_NAME - value: "$(MY_POD_NAME)" - - name: ETCD_DATA_DIR - value: "/bitnami/etcd/data" - - name: ETCD_LOG_LEVEL - value: "info" - - name: ALLOW_NONE_AUTHENTICATION - value: "yes" - - name: ETCD_ADVERTISE_CLIENT_URLS - value: "http://$(MY_POD_NAME).openserverless-milvus-etcd-headless.openserverless.svc.cluster.local:2379" - - name: ETCD_LISTEN_CLIENT_URLS - value: "http://0.0.0.0:2379" - - name: ETCD_INITIAL_ADVERTISE_PEER_URLS - value: "http://$(MY_POD_NAME).openserverless-milvus-etcd-headless.openserverless.svc.cluster.local:2380" - - name: ETCD_LISTEN_PEER_URLS - value: "http://0.0.0.0:2380" - - name: ETCD_AUTO_COMPACTION_MODE - value: "revision" - - name: ETCD_AUTO_COMPACTION_RETENTION - value: "1000" - - name: ETCD_QUOTA_BACKEND_BYTES - value: "4294967296" - - name: ETCD_HEARTBEAT_INTERVAL - value: "500" - - name: ETCD_ELECTION_TIMEOUT - value: "2500" - envFrom: - ports: - - name: client - containerPort: 2379 - protocol: TCP - - name: peer - containerPort: 2380 - protocol: TCP - livenessProbe: - exec: - command: - - /opt/bitnami/scripts/etcd/healthcheck.sh - initialDelaySeconds: 60 - periodSeconds: 30 - timeoutSeconds: 10 - successThreshold: 1 - failureThreshold: 5 - readinessProbe: - exec: - command: - - /opt/bitnami/scripts/etcd/healthcheck.sh - initialDelaySeconds: 60 - periodSeconds: 20 - timeoutSeconds: 10 - successThreshold: 1 - failureThreshold: 5 - resources: - limits: {} - requests: {} - volumeMounts: - - name: data - mountPath: /bitnami/etcd - volumes: - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: - - "ReadWriteOnce" - resources: - requests: - storage: "10Gi" ---- -# Source: milvus/charts/pulsarv3/templates/autorecovery-statefulset.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: "openserverless-milvus-pulsarv3-recovery" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: recovery -spec: - serviceName: "openserverless-milvus-pulsarv3-recovery" - replicas: 1 - updateStrategy: - type: RollingUpdate - podManagementPolicy: Parallel - # nodeSelector: - selector: - matchLabels: - app: pulsarv3 - release: openserverless-milvus - component: recovery - template: - metadata: - labels: - app: pulsarv3 - release: openserverless-milvus - cluster: openserverless-milvus-pulsarv3 - component: recovery - annotations: - prometheus.io/scrape: "true" - prometheus.io/port: "8000" - spec: - affinity: - terminationGracePeriodSeconds: 30 - serviceAccountName: "openserverless-milvus-pulsarv3-recovery" - initContainers: - # This initContainer will wait for bookkeeper initnewcluster to complete - # before deploying the bookies - - name: pulsar-bookkeeper-verify-clusterid - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - resources: - requests: - cpu: 0.1 - memory: 256Mi - command: ["sh", "-c"] - args: - - > - bin/apply-config-from-env.py conf/bookkeeper.conf;until bin/bookkeeper shell whatisinstanceid; do - sleep 3; - done; - envFrom: - - configMapRef: - name: "openserverless-milvus-pulsarv3-recovery" - volumeMounts: - - containers: - - name: "openserverless-milvus-pulsarv3-recovery" - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - resources: - requests: - cpu: 0.1 - memory: 128Mi - command: ["sh", "-c"] - args: - - > - bin/apply-config-from-env.py conf/bookkeeper.conf; - - OPTS="${OPTS} -Dlog4j2.formatMsgNoLookups=true" exec bin/bookkeeper autorecovery - ports: - - name: http - containerPort: 8000 - envFrom: - - configMapRef: - name: "openserverless-milvus-pulsarv3-recovery" - volumeMounts: - - volumes: ---- -# Source: milvus/charts/pulsarv3/templates/bookkeeper-statefulset.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: "openserverless-milvus-pulsarv3-bookie" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: bookie -spec: - serviceName: "openserverless-milvus-pulsarv3-bookie" - replicas: 3 - selector: - matchLabels: - app: pulsarv3 - release: openserverless-milvus - component: bookie - updateStrategy: - type: RollingUpdate - podManagementPolicy: Parallel - template: - metadata: - labels: - app: pulsarv3 - release: openserverless-milvus - cluster: openserverless-milvus-pulsarv3 - component: bookie - annotations: - prometheus.io/scrape: "true" - prometheus.io/port: "8000" - spec: - affinity: - terminationGracePeriodSeconds: 30 - serviceAccountName: "openserverless-milvus-pulsarv3-bookie" - securityContext: - fsGroup: 0 - fsGroupChangePolicy: OnRootMismatch - initContainers: - # This initContainer will wait for bookkeeper initnewcluster to complete - # before deploying the bookies - - name: pulsar-bookkeeper-verify-clusterid - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - resources: - requests: - cpu: 0.1 - memory: 256Mi - command: ["sh", "-c"] - args: - # only reformat bookie if bookkeeper is running without persistence - - > - - set -e; - bin/apply-config-from-env.py conf/bookkeeper.conf;until bin/bookkeeper shell whatisinstanceid; do - sleep 3; - done; - envFrom: - - configMapRef: - name: "openserverless-milvus-pulsarv3-bookie" - volumeMounts: - - containers: - - name: "openserverless-milvus-pulsarv3-bookie" - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /api/v1/bookie/state - port: 8000 - initialDelaySeconds: 10 - periodSeconds: 30 - timeoutSeconds: 5 - failureThreshold: 60 - readinessProbe: - httpGet: - path: /api/v1/bookie/is_ready - port: 8000 - initialDelaySeconds: 10 - periodSeconds: 30 - timeoutSeconds: 5 - failureThreshold: 60 - resources: - requests: - cpu: 0.5 - memory: 2048Mi - command: ["sh", "-c"] - args: - - > - bin/apply-config-from-env.py conf/bookkeeper.conf; - - OPTS="${OPTS} -Dlog4j2.formatMsgNoLookups=true" exec bin/pulsar bookie; - ports: - - name: "bookie" - containerPort: 3181 - - name: http - containerPort: 8000 - envFrom: - - configMapRef: - name: "openserverless-milvus-pulsarv3-bookie" - volumeMounts: - - name: "openserverless-milvus-pulsarv3-bookie-journal" - mountPath: /pulsar/data/bookkeeper/journal - - name: "openserverless-milvus-pulsarv3-bookie-ledgers" - mountPath: /pulsar/data/bookkeeper/ledgers - - volumes: - - - volumeClaimTemplates: - - metadata: - name: "openserverless-milvus-pulsarv3-bookie-journal" - spec: - accessModes: [ "ReadWriteOnce" ] - resources: - requests: - storage: 100Gi - - metadata: - name: "openserverless-milvus-pulsarv3-bookie-ledgers" - spec: - accessModes: [ "ReadWriteOnce" ] - resources: - requests: - storage: 200Gi ---- -# Source: milvus/charts/pulsarv3/templates/broker-statefulset.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: "openserverless-milvus-pulsarv3-broker" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: broker -spec: - serviceName: "openserverless-milvus-pulsarv3-broker" - replicas: 2 - selector: - matchLabels: - app: pulsarv3 - release: openserverless-milvus - component: broker - updateStrategy: - type: RollingUpdate - podManagementPolicy: Parallel - template: - metadata: - labels: - app: pulsarv3 - release: openserverless-milvus - cluster: openserverless-milvus-pulsarv3 - component: broker - annotations: - prometheus.io/scrape: "true" - prometheus.io/port: "8080" - spec: - serviceAccountName: "openserverless-milvus-pulsarv3-broker-acct" - affinity: - terminationGracePeriodSeconds: 30 - initContainers: - # This init container will wait for zookeeper to be ready before - # deploying the bookies - - name: wait-zookeeper-ready - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - resources: - requests: - cpu: 0.1 - memory: 256Mi - command: ["sh", "-c"] - args: - - >- - - export BOOKIE_MEM="-Xmx128M"; - until bin/bookkeeper org.apache.zookeeper.ZooKeeperMain -server openserverless-milvus-pulsarv3-zookeeper:2181 get /admin/clusters/openserverless-milvus-pulsarv3; do - echo "pulsar cluster openserverless-milvus-pulsarv3 isn't initialized yet ... check in 3 seconds ..." && sleep 3; - done; - volumeMounts: - - # This init container will wait for bookkeeper to be ready before - # deploying the broker - - name: wait-bookkeeper-ready - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - resources: - requests: - cpu: 0.1 - memory: 256Mi - command: ["sh", "-c"] - args: - - > - - bin/apply-config-from-env.py conf/bookkeeper.conf; - export BOOKIE_MEM="-Xmx128M"; - until bin/bookkeeper shell whatisinstanceid; do - echo "bookkeeper cluster is not initialized yet. backoff for 3 seconds ..."; - sleep 3; - done; - echo "bookkeeper cluster is already initialized"; - bookieServiceNumber="$(nslookup -timeout=10 openserverless-milvus-pulsarv3-bookie | grep Name | wc -l)"; - until [ ${bookieServiceNumber} -ge 3 ]; do - echo "bookkeeper cluster openserverless-milvus-pulsarv3 isn't ready yet ... check in 10 seconds ..."; - sleep 10; - bookieServiceNumber="$(nslookup -timeout=10 openserverless-milvus-pulsarv3-bookie | grep Name | wc -l)"; - done; - echo "bookkeeper cluster is ready"; - envFrom: - - configMapRef: - name: "openserverless-milvus-pulsarv3-bookie" - volumeMounts: - - containers: - - name: "openserverless-milvus-pulsarv3-broker" - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /status.html - port: 8080 - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 10 - readinessProbe: - httpGet: - path: /status.html - port: 8080 - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 10 - resources: - requests: - cpu: 0.5 - memory: 2048Mi - command: ["sh", "-c"] - args: - - > - bin/apply-config-from-env.py conf/broker.conf; - bin/gen-yml-from-env.py conf/functions_worker.yml; - echo "OK" > status; - - bin/pulsar zookeeper-shell -server openserverless-milvus-pulsarv3-zookeeper:2181 get /loadbalance/brokers/${HOSTNAME}.openserverless-milvus-pulsarv3-broker.openserverless.svc.cluster.local:8080; - while [ $? -eq 0 ]; do - echo "broker ${HOSTNAME}.openserverless-milvus-pulsarv3-broker.openserverless.svc.cluster.local znode still exists ... check in 10 seconds ..."; - sleep 10; - bin/pulsar zookeeper-shell -server openserverless-milvus-pulsarv3-zookeeper:2181 get /loadbalance/brokers/${HOSTNAME}.openserverless-milvus-pulsarv3-broker.openserverless.svc.cluster.local:8080; - done; - cat conf/pulsar_env.sh; - OPTS="${OPTS} -Dlog4j2.formatMsgNoLookups=true" exec bin/pulsar broker; - ports: - # prometheus needs to access /metrics endpoint - - name: http - containerPort: 8080 - - name: "pulsar" - containerPort: 6650 - envFrom: - - configMapRef: - name: "openserverless-milvus-pulsarv3-broker" - volumeMounts: - - env: - volumes: ---- -# Source: milvus/charts/pulsarv3/templates/proxy-statefulset.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: "openserverless-milvus-pulsarv3-proxy" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: proxy -spec: - serviceName: "openserverless-milvus-pulsarv3-proxy" - replicas: 2 - selector: - matchLabels: - app: pulsarv3 - release: openserverless-milvus - component: proxy - updateStrategy: - type: RollingUpdate - podManagementPolicy: Parallel - template: - metadata: - labels: - app: pulsarv3 - release: openserverless-milvus - cluster: openserverless-milvus-pulsarv3 - component: proxy - annotations: - prometheus.io/scrape: "true" - prometheus.io/port: "80" - spec: - affinity: - terminationGracePeriodSeconds: 30 - serviceAccountName: "openserverless-milvus-pulsarv3-proxy" - initContainers: - # This init container will wait for zookeeper to be ready before - # deploying the bookies - - name: wait-zookeeper-ready - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - resources: - requests: - cpu: 0.1 - memory: 256Mi - command: ["sh", "-c"] - args: - - >- - export PULSAR_MEM="-Xmx128M"; - until bin/pulsar zookeeper-shell -server openserverless-milvus-pulsarv3-zookeeper get /admin/clusters/openserverless-milvus-pulsarv3; do - sleep 3; - done; - # This init container will wait for at least one broker to be ready before - # deploying the proxy - - name: wait-broker-ready - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - resources: - requests: - cpu: 0.1 - memory: 256Mi - command: ["sh", "-c"] - args: - - >- - set -e; - brokerServiceNumber="$(nslookup -timeout=10 openserverless-milvus-pulsarv3-broker | grep Name | wc -l)"; - until [ ${brokerServiceNumber} -ge 1 ]; do - echo "pulsar cluster openserverless-milvus-pulsarv3 isn't initialized yet ... check in 10 seconds ..."; - sleep 10; - brokerServiceNumber="$(nslookup -timeout=10 openserverless-milvus-pulsarv3-broker | grep Name | wc -l)"; - done; - containers: - - name: "openserverless-milvus-pulsarv3-proxy" - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /status.html - port: 8080 - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 10 - readinessProbe: - httpGet: - path: /status.html - port: 8080 - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 10 - resources: - requests: - cpu: 0.5 - memory: 1024Mi - command: ["sh", "-c"] - args: - - > - bin/apply-config-from-env.py conf/proxy.conf && - echo "OK" > status && - OPTS="${OPTS} -Dlog4j2.formatMsgNoLookups=true" exec bin/pulsar proxy - ports: - # prometheus needs to access /metrics endpoint - - name: sts-http - containerPort: 8080 - - name: "sts-pulsar" - containerPort: 6650 - envFrom: - - configMapRef: - name: "openserverless-milvus-pulsarv3-proxy" ---- -# Source: milvus/charts/pulsarv3/templates/zookeeper-statefulset.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# deploy zookeeper only when `components.zookeeper` is true -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: "openserverless-milvus-pulsarv3-zookeeper" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: zookeeper -spec: - serviceName: "openserverless-milvus-pulsarv3-zookeeper" - replicas: 3 - selector: - matchLabels: - app: pulsarv3 - release: openserverless-milvus - component: zookeeper - updateStrategy: - type: RollingUpdate - podManagementPolicy: Parallel - template: - metadata: - labels: - app: pulsarv3 - release: openserverless-milvus - cluster: openserverless-milvus-pulsarv3 - component: zookeeper - annotations: - spec: - affinity: - terminationGracePeriodSeconds: 30 - serviceAccountName: "openserverless-milvus-pulsarv3-zookeeper" - securityContext: - fsGroup: 0 - fsGroupChangePolicy: OnRootMismatch - containers: - - name: "openserverless-milvus-pulsarv3-zookeeper" - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - resources: - requests: - cpu: 0.2 - memory: 256Mi - command: ["sh", "-c"] - args: - - > - bin/apply-config-from-env.py conf/zookeeper.conf; - - bin/generate-zookeeper-config.sh conf/zookeeper.conf; - OPTS="${OPTS} -Dlog4j2.formatMsgNoLookups=true" exec bin/pulsar zookeeper; - ports: - # prometheus needs to access /metrics endpoint - - name: http - containerPort: 8000 - - name: client - containerPort: 2181 - - name: follower - containerPort: 2888 - - name: leader-election - containerPort: 3888 - env: - - name: ZOOKEEPER_SERVERS - value: openserverless-milvus-pulsarv3-zookeeper-0,openserverless-milvus-pulsarv3-zookeeper-1,openserverless-milvus-pulsarv3-zookeeper-2 - - name: EXTERNAL_PROVIDED_SERVERS - value: "false" - envFrom: - - configMapRef: - name: "openserverless-milvus-pulsarv3-zookeeper" - readinessProbe: - exec: - command: - - timeout - - "30" - - bash - - -c - - 'echo ruok | nc -q 1 localhost 2181 | grep imok' - initialDelaySeconds: 20 - periodSeconds: 30 - timeoutSeconds: 30 - failureThreshold: 10 - livenessProbe: - exec: - command: - - timeout - - "30" - - bash - - -c - - 'echo ruok | nc -q 1 localhost 2181 | grep imok' - initialDelaySeconds: 20 - periodSeconds: 30 - timeoutSeconds: 30 - failureThreshold: 10 - volumeMounts: - - name: "openserverless-milvus-pulsarv3-zookeeper-data" - mountPath: /pulsar/data - volumes: - - volumeClaimTemplates: - - metadata: - name: "openserverless-milvus-pulsarv3-zookeeper-data" - spec: - accessModes: [ "ReadWriteOnce" ] - resources: - requests: - storage: 20Gi ---- -# Source: milvus/charts/pulsarv3/templates/bookkeeper-cluster-initialize.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: batch/v1 -kind: Job -metadata: - name: "openserverless-milvus-pulsarv3-bookie-init" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: "bookie-init" -spec: -# This feature was previously behind a feature gate for several Kubernetes versions and will default to true in 1.23 and beyond -# https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/ - template: - spec: - - serviceAccountName: "openserverless-milvus-pulsarv3-bookie" - nodeSelector: - tolerations: - initContainers: - - name: wait-zookeeper-ready - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - resources: - requests: - cpu: 0.1 - memory: 256Mi - command: ["sh", "-c"] - args: - - >- - until nslookup openserverless-milvus-pulsarv3-zookeeper-2.openserverless-milvus-pulsarv3-zookeeper.openserverless; do - sleep 3; - done; - containers: - - name: "openserverless-milvus-pulsarv3-bookie-init" - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - command: ["sh", "-c"] - args: - - > - bin/apply-config-from-env.py conf/bookkeeper.conf; - - export BOOKIE_MEM="-Xmx128M"; - if bin/bookkeeper shell whatisinstanceid; then - echo "bookkeeper cluster already initialized"; - else - bin/bookkeeper shell initnewcluster; - fi - envFrom: - - configMapRef: - name: "openserverless-milvus-pulsarv3-bookie" - volumeMounts: - - volumes: - - restartPolicy: OnFailure ---- -# Source: milvus/charts/pulsarv3/templates/pulsar-cluster-initialize.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: batch/v1 -kind: Job -metadata: - name: "openserverless-milvus-pulsarv3-pulsar-init" - namespace: openserverless - labels: - app: pulsarv3 - chart: pulsarv3-3.3.0 - release: openserverless-milvus - heritage: Helm - cluster: openserverless-milvus-pulsarv3 - component: pulsar-init -spec: -# This feature was previously behind a feature gate for several Kubernetes versions and will default to true in 1.23 and beyond -# https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/ - template: - spec: - - initContainers: - - name: wait-zookeeper-ready - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - resources: - requests: - cpu: 0.1 - memory: 256Mi - command: ["sh", "-c"] - args: - - >- - until nslookup openserverless-milvus-pulsarv3-zookeeper-2.openserverless-milvus-pulsarv3-zookeeper.openserverless; do - sleep 3; - done; - # This initContainer will wait for bookkeeper initnewcluster to complete - # before initializing pulsar metadata - - name: pulsar-bookkeeper-verify-clusterid - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - resources: - requests: - cpu: 0.1 - memory: 256Mi - command: ["sh", "-c"] - args: - - > - bin/apply-config-from-env.py conf/bookkeeper.conf; - echo Default BOOKIE_MEM settings are set very high, which can cause the init container to fail.; - echo Setting the memory to a lower value to avoid OOM as operations below are not memory intensive.; - export BOOKIE_MEM="-Xmx128M"; - - until bin/bookkeeper shell whatisinstanceid; do - sleep 3; - done; - envFrom: - - configMapRef: - name: "openserverless-milvus-pulsarv3-bookie" - volumeMounts: - - containers: - - name: "openserverless-milvus-pulsarv3-pulsar-init" - image: "apachepulsar/pulsar:3.0.7" - imagePullPolicy: IfNotPresent - command: ["sh", "-c"] - args: - - | - - export PULSAR_MEM="-Xmx128M"; - bin/pulsar initialize-cluster-metadata \ - --cluster openserverless-milvus-pulsarv3 \ - --zookeeper openserverless-milvus-pulsarv3-zookeeper:2181 \ - --configuration-store openserverless-milvus-pulsarv3-zookeeper:2181 \ - --web-service-url http://openserverless-milvus-pulsarv3-broker.openserverless.svc.cluster.local:8080/ \ - --web-service-url-tls https://openserverless-milvus-pulsarv3-broker.openserverless.svc.cluster.local:8443/ \ - --broker-service-url pulsar://openserverless-milvus-pulsarv3-broker.openserverless.svc.cluster.local:6650/ \ - --broker-service-url-tls pulsar+ssl://openserverless-milvus-pulsarv3-broker.openserverless.svc.cluster.local:6651/ ; - volumeMounts: - - volumes: - - restartPolicy: OnFailure ---- -# Source: milvus/charts/pulsarv3/templates/autorecovery-podmonitor.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# deploy broker PodMonitor only when `$.Values.broker.podMonitor.enabled` is true ---- -# Source: milvus/charts/pulsarv3/templates/autorecovery-psp.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/bookkeeper-pdb.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/bookkeeper-podmonitor.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# deploy bookkeeper PodMonitor only when `$.Values.bookkeeper.podMonitor.enabled` is true ---- -# Source: milvus/charts/pulsarv3/templates/bookkeeper-psp.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/bookkeeper-storageclass.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/broker-hpa.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/broker-pdb.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/broker-podmonitor.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# deploy broker PodMonitor only when `$.Values.broker.podMonitor.enabled` is true ---- -# Source: milvus/charts/pulsarv3/templates/broker-psp.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/broker-rbac.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/check_helm_version.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/keytool.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# script to process key/cert to keystore and truststore ---- -# Source: milvus/charts/pulsarv3/templates/namespace.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/proxy-hpa.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/proxy-ingress.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/proxy-pdb.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/proxy-podmonitor.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# deploy proxy PodMonitor only when `$.Values.proxy.podMonitor.enabled` is true ---- -# Source: milvus/charts/pulsarv3/templates/proxy-psp.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/pulsar-manager-admin-secret.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/pulsar-manager-cluster-initialize.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/pulsar-manager-configmap.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/pulsar-manager-ingress.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/pulsar-manager-service.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/pulsar-manager-statefulset.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/tls-cert-internal-issuer.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/tls-certs-internal.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/toolset-configmap.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/toolset-psp.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/toolset-service-account.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/toolset-service.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/toolset-statefulset.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/zookeeper-pdb.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# deploy zookeeper only when `components.zookeeper` is true ---- -# Source: milvus/charts/pulsarv3/templates/zookeeper-podmonitor.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# deploy zookeeper PodMonitor only when `$.Values.zookeeper.podMonitor.enabled` is true ---- -# Source: milvus/charts/pulsarv3/templates/zookeeper-psp.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -# Source: milvus/charts/pulsarv3/templates/zookeeper-storageclass.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# deploy zookeeper only when `components.zookeeper` is true - -# define the storage class for data directory diff --git a/deploy/etcd/etcd-jwt-secret.yaml b/deploy/etcd/etcd-jwt-secret.yaml deleted file mode 100644 index 0841f86e..00000000 --- a/deploy/etcd/etcd-jwt-secret.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ---- -apiVersion: v1 -kind: Secret -metadata: - name: openserverless-etcd-jwt-token - namespace: openserverless - labels: - app.kubernetes.io/name: openserverless-etcd -type: Opaque -data: - jwt-token.pem: "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS1FJQkFBS0NBZ0VBdXlNcjdmOUJWajJ2RjhNUC8zNStrcnA0MlF0Nk5YMmEvS3IyY24xNjBSajBuZ0lvCk1qL1RmdXVnMmJnaHlEdnhDdytKZU1QUUNYMXRGZENzM2RHZ0xmSFppM0MyR21VV250VG1WQTRCSENZdmc4RisKRnBYQURjdE5EKzRWc1F4OUVsN1pKR0lxWFkySThFM3JPWkd4SnhxQmR1bDIwVy9xczNSNFFRMlFsbzU0dzNhagpmVWZCQ3FoeTdveXJWclN5NFgwN0JYL09CYkRCU2NBNUJlTG9ZOFdTNWwvYWFvZnRiVlpqYmxHYkE5S0tvclFNCmN6QjNxenBzQXZyUjZkWVBES1dFMVpGQ1huWjBBdjBsQlZMckk4S1ZkREFaWHBRelFEUDl5dytCMWpaelhiaG0KRGg5UmN6YUt3dXJ5UTA4d0JVanpUU09ydkdaY254aEJQUjZ5dTUwUzYranJEZGU0b2J0anpYTHZGQkFmdXNLSQpLczZtRG1sYUpKVGVybUlQNnFZOHZKRUx6eTNYYk40R2tyNk5NMjZFdkxnTnRXYmhtSVBzRysyRVBTelErV3JaCmFsTFNVcFRSU0kzRjl5THFrN21ybE9ZZUxxTjRoYU1rem4ySFc2RXhZL25kSjJJMXBVUWZIQ3d5Y0MxMDNhSDUKN29HZ01DTGpWdnBENjFMZ3hXa3U3dnRGcjI0L1lpS3B1SGc5V21UQlNKY05CcVJ4b3l5UFlRRWJsNjBFLy80UgpLc01XenJLdm9qeFlXQ1ZsLzV1ZElnUGoraDM1VkZ6L3VxOExvLy83QkZ6WWRqRWhLZXc0cHBPc2xIMXlkY3FsCmNPRHNQQUg4L1UxbFZpUzFaY0FRV3V6QWo2b3ppRWlrd3F1RnFYMHdOQnNURkt2YWxiNmYvUFBHc2FVQ0F3RUEKQVFLQ0FnQktVWmFCcHl5Z2ZsREtCQXFYY2ZuOGRaZkd3cnNmQVNvTERDNWhtNW1wN3RvKzRpWnFobFNOQkREVwpKajBzVXpuUUpiUjN4SFFiUUxlUXdWWFZwZ21TRllsS3lsRlh2Q3UybW1CNm96RW5wN2FaQWIvOFlpNXFqQUduCndPcldiRnBEY0cyVitISlF3d1BPUTNPV1lsalRhY0JlcENzdTA5Nkd0RXVKc2tSb3RsNFpha1ZWWEtpNmZkbGYKQ1hpR0w2OFd3L0FycCt6Kzc0cGpxclk2c3ptTlhncVArU001eVkrdUsyL0RnUjhwN25SbVYxRFdjam9rUTBoZwpDcEpRNWxVRnRicENRclR6NU9EL3l0N245VjB5ejNVaE5uVHN6YmhvMS8wRHByM3VmNk5qWmZjSnhzQkhIRE1SClk5TWdqaE5KRmdhaDdQZW9FNUxYRC8vZ3NROVRxZHZmOGpsT2d1OWFzWGF6eGtiNGw4NTc2czVTNkJtbUN5QlIKTG1USGVTSGpvLzBVQ0psU0pWUTZsdDZNZ2FZRHVBMXNZZjhDdkdtT0doRjQzM1pyUWtqTkdSYnZPTVg2R3luegp1aDlvVWhndDFuaFVNSE5iaEZvNjZRbW1tNERmMVJudnpFbTVJVmk1MXY0dnE1eWI5OUFSd0dBQmg1S3V5d292Cm9QaU9OZndwRlF3WGRzWlo2cllDcFJjMUpVbHE0Q25CcnkxbTQ4eDRNNURmakxjaDVmelNLakg1UFd5ZnE2SkIKditPUzRSalZtMlljRThzQk5iVXcyYUF2TW5OQzNLWE5RbFhuV1IzVGg2T05veXhqc3lwN3ErTHNSdTd3RkZlWgoyUTFya21IQ3kyWlFsOFRSeUhObzZ1UXc1aTJ2SGZEWE5abm8vcmZwNTZuUEoxQk5vUUtDQVFFQTlzM0tMSVJlCjJpN0VoekZiRSsvRHNMRnJjeXhlWlJ5V2x4dXR1Q3BCZk1ET0tPTDhtYjhsT1MxcnRzTDZSSit0a0ZhYmFYeXkKTXJoM3dxaW5zazBBRVM5QlMyeU1YaU5PT3RSbjljZkRHMVpPcU1vOXYwVFJXRUZtUlFTQ0x1QThPeXlKZmZKcAorV2psTDNPZU5RTzB4Sk9pNVFxbzVEdDVwSnhvZEl0NXVlOHR1ZUVjaWt6Wm80aUF0Q3oweUovYWVpR3VyOWgwCkZyZlFHL1BGeGtWeVdia210U2lwM3NRbE03d29RZ3Y2ay9rV3NIQ1hZNjI0YzN4cytieThrczRLYzVlSGZxeXAKQUxNZ0NnNXFuVjBFbHdEWThCd1l1NlFETHZsdlF1cVNqNlB4NFRjY2I2NWFJUy9QUm5GOEtQbWZlV3kxaWs5Zgp2SmxSaWNQaWtWMS95UUtDQVFFQXdodzhZV3FLdG90R050UkdieVArVXRTT252OXhIR05rUjRzN0ZRYVltNGF0ClB4SjZlbkJGd3F5VkV6b1VuNkk1cGdhQnRHNzBDdHRQanR2R0lzVkZ2S3BGMGdRZSsrdkQwNHl6RW4rUHFBSWUKUkVCd0NmSWFKWTVWMFJwUHZRdnlrNDFDWG5rTlRsZXc0eEVtV1Zlc1l0U1Fwb3pwMllJeWlYcjhuWjY0a1hsKwp1cDBWdUc2VENPTllhbnhQUTFEbFViOCtzZlBtaCtqSC84U1o5d3ppK0pBQUlIT2VaVktWSFFGcEJPWDB1dU1BCngwR0VVbXN2QlJVeXRBM0RJenEraXRGMFdUeWRQNlR5UU1HaTJVamYrcHNObVhJODgxOVluSHlRaGVOeEZ4aVUKK2dpNDVXQ1JEbFEzWmIrdmZzRWo5S3o5QUl4N1lObmljNFI4Znlnby9RS0NBUUE1Q2JKUnUzM1lXcnFjcS9GcQptQnpYdHdrb0l2YmwwMGVTUUpsVFdLQ3QzamxIQmI1dklkZk9jUXJWd0JYcXlpUm9tVHR0azUzelR1S09vRCt4ClcwNUtxbXNpclVGN2VCbGFTMENEY3RsS2RoWFR1SG13dkl6TklzQXA3ekxPOW8rMDlVUUpVWDdnWHplb1VjWWMKWm5EZWhXZytHc3FSaEpWR1Q4ZHNOOWUzK3VqTWJwUVQrbExLRW4vOEkrK21kaHNkNHE3bWdOWERjbE9zUWhoUgpERGV6T0xFVGFRUnBNVmVQcjBwMmlTSXVGSUVENGVGNjdla1U5NHFQNU9pSDNRcFpVSW5GNHMxa0ZnR3ZrVWxlCmVPTFlxeDkzem1hQTV4UzlSSFJNT0w0S2FFODhSMnBRL1RCalB4TmVLbGdSZUM4ODlyeTE3RVlwNWgwTUE3TlcKK1E3UkFvSUJBUUNqa0pkd253NXo4N2dleTVEdVppNFFMZnhnRG0rSnRGYnowbWJldU1ndmhiVEd5Uk5ieU85LwpzMTJLN2c0aTFkZ3VCcDJWSlVtUFBHNDF1d3Vpa1FqS2lkQjd2KzA3NnRjQWxEYnhCM3hOM2pzUzJCbVF4K2t3CmJGZno4Mi90cFB1clNnVDdCcFlueGN6UUNjdCt6Vm45alpDMlIxZG1hVWVjZ1g0TWM0cWpTdlYyMnBkSzZpSU0KQTBhbHRFbzJOeHlGWGRtK3FmOC8xdTl3bm9IK28yVStmbGtjLzFBaG9RejFmQks2aGV0Szc2M2dPVnorOWtLUQp6ZVFEeFBVQjRxWmM0MUpmUDZ4aHpPUTFuUFZVa3d2cGFHNTFOc1lWYmVxRk1wRzF3SW16Skl1a2hXVUlJKzhYClcrK3lWV3ZVSzN2WTJVdjRBbWlVbkFVdHdoNEdLQmtoQW9JQkFRQ2gzeHk3Uk50QjUzREVUYndJbUtlUUxscGgKTXQzNWZLdWdwRTFDb1Y5WTNVZEpaczdlQ1YyN2pzcEFHZ3VxKytzODg1MEw4L3RuVGd5SHZWbVBlSzZGdW5GTgpEak9uNUNHOHByYVNmTVAvNTZjN282R29aRk1NUUtoZDBzOFczODN0Q2Jobk9YQng1S1FOa2hMNWQ2TlZrQzB2Cnd2WGpMUmdud2IxNlJnaGhESUQwVFhYNHc1UXlMVGVkTEYwZnc5Z0ZPeHY4VFhteWVzOGNVdzh5a3RnUEhMYTAKM1dFbzR1U1N4eE9zQTB4QzZ2dTZZM3VybWtVWTluYWpGUDcwaUtMT1dBWkozVkFQeTUxS2kzU2xNRHBRRmpCTgpmcXg2aXFvUGU2L0ZKc29xcHVoN29UNXEyRzhkMWdWa3h1ak1MQXZ5Z2pHQ2JtczVVV3ZFNS9TQ2hvMU0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K" \ No newline at end of file diff --git a/deploy/etcd/etcd-sts.yaml b/deploy/etcd/etcd-sts.yaml index f3d915f8..1e828b6e 100644 --- a/deploy/etcd/etcd-sts.yaml +++ b/deploy/etcd/etcd-sts.yaml @@ -152,7 +152,7 @@ spec: - name: etcd-jwt-token secret: defaultMode: 256 - secretName: openserverless-etcd-jwt-token + secretName: openserverless-etcd-jwt-token-v2 volumeClaimTemplates: - metadata: name: openserverless-etc-data diff --git a/deploy/kafka/kafka-001-sts.yaml b/deploy/kafka/kafka-001-sts.yaml index 0c0f8594..e8b7e369 100644 --- a/deploy/kafka/kafka-001-sts.yaml +++ b/deploy/kafka/kafka-001-sts.yaml @@ -33,6 +33,8 @@ spec: name: kafka template: metadata: + annotations: + openserverless.org/messaging-isolation: "v1" labels: name: kafka app: kafka @@ -104,4 +106,4 @@ spec: volumeMounts: - mountPath: /kafka name: kafka-pvc - readOnly: true \ No newline at end of file + readOnly: true diff --git a/deploy/kafka/kafka-004-networkpolicy.yaml b/deploy/kafka/kafka-004-networkpolicy.yaml new file mode 100644 index 00000000..b79276be --- /dev/null +++ b/deploy/kafka/kafka-004-networkpolicy.yaml @@ -0,0 +1,51 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: openserverless-kafka-isolation + namespace: openserverless +spec: + podSelector: + matchLabels: + name: kafka + policyTypes: [Ingress] + ingress: + - from: + - podSelector: + matchExpressions: + - key: name + operator: In + values: [controller, invoker, scheduler, kafka] + - key: user-action-pod + operator: DoesNotExist + ports: + - port: 9092 + protocol: TCP + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: prometheus + app.kubernetes.io/instance: openserverless-prometheus + matchExpressions: + - key: user-action-pod + operator: DoesNotExist + ports: + - port: 9071 + protocol: TCP + - port: 9888 + protocol: TCP diff --git a/deploy/milvus-operator/README.md b/deploy/milvus-operator/README.md index 25cb2d66..2d5321c8 100644 --- a/deploy/milvus-operator/README.md +++ b/deploy/milvus-operator/README.md @@ -19,4 +19,4 @@ # Milvus Operator -Left for reference for future integration of the Milvus Operator. Currenlty Apache OpenServerless deploys an Standalone MILVUS setup, reusing existing MINIO and ETCD. \ No newline at end of file +Left for reference for future integration of the Milvus Operator. Currenlty Apache OpenServerless deploys an Standalone MILVUS setup, reusing existing SEAWEEDFS and ETCD. \ No newline at end of file diff --git a/deploy/milvus-slim/milvus-cfg-deps.yaml b/deploy/milvus-slim/milvus-cfg-deps.yaml deleted file mode 100644 index af56c35d..00000000 --- a/deploy/milvus-slim/milvus-cfg-deps.yaml +++ /dev/null @@ -1,116 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: openserverless-milvus-minio -data: - initialize: |- - #!/bin/sh - set -e ; # Have script exit in the event of a failed command. - MC_CONFIG_DIR="/etc/minio/mc/" - MC="/usr/bin/mc --insecure --config-dir ${MC_CONFIG_DIR}" - - # connectToMinio - # Use a check-sleep-check loop to wait for Minio service to be available - connectToMinio() { - SCHEME=$1 - ATTEMPTS=0 ; LIMIT=29 ; # Allow 30 attempts - set -e ; # fail if we can't read the keys. - ACCESS=$(cat /config/accesskey) ; SECRET=$(cat /config/secretkey) ; - set +e ; # The connections to minio are allowed to fail. - echo "Connecting to Minio server: $SCHEME://$MINIO_ENDPOINT:$MINIO_PORT" ; - MC_COMMAND="${MC} config host add myminio $SCHEME://$MINIO_ENDPOINT:$MINIO_PORT $ACCESS $SECRET" ; - $MC_COMMAND ; - STATUS=$? ; - until [ $STATUS = 0 ] - do - ATTEMPTS=`expr $ATTEMPTS + 1` ; - echo \"Failed attempts: $ATTEMPTS\" ; - if [ $ATTEMPTS -gt $LIMIT ]; then - exit 1 ; - fi ; - sleep 2 ; # 1 second intervals between attempts - $MC_COMMAND ; - STATUS=$? ; - done ; - set -e ; # reset `e` as active - return 0 - } - - # checkBucketExists ($bucket) - # Check if the bucket exists, by using the exit code of `mc ls` - checkBucketExists() { - BUCKET=$1 - CMD=$(${MC} ls myminio/$BUCKET > /dev/null 2>&1) - return $? - } - - # createBucket ($bucket, $policy, $purge) - # Ensure bucket exists, purging if asked to - createBucket() { - BUCKET=$1 - POLICY=$2 - PURGE=$3 - VERSIONING=$4 - - # Purge the bucket, if set & exists - # Since PURGE is user input, check explicitly for `true` - if [ $PURGE = true ]; then - if checkBucketExists $BUCKET ; then - echo "Purging bucket '$BUCKET'." - set +e ; # don't exit if this fails - ${MC} rm -r --force myminio/$BUCKET - set -e ; # reset `e` as active - else - echo "Bucket '$BUCKET' does not exist, skipping purge." - fi - fi - - # Create the bucket if it does not exist - if ! checkBucketExists $BUCKET ; then - echo "Creating bucket '$BUCKET'" - ${MC} mb myminio/$BUCKET - else - echo "Bucket '$BUCKET' already exists." - fi - - - # set versioning for bucket - if [ ! -z $VERSIONING ] ; then - if [ $VERSIONING = true ] ; then - echo "Enabling versioning for '$BUCKET'" - ${MC} version enable myminio/$BUCKET - elif [ $VERSIONING = false ] ; then - echo "Suspending versioning for '$BUCKET'" - ${MC} version suspend myminio/$BUCKET - fi - else - echo "Bucket '$BUCKET' versioning unchanged." - fi - - # At this point, the bucket should exist, skip checking for existence - # Set policy on the bucket - echo "Setting policy of bucket '$BUCKET' to '$POLICY'." - ${MC} policy set $POLICY myminio/$BUCKET - } - - # Try connecting to Minio instance - scheme=http - connectToMinio $scheme diff --git a/deploy/milvus-slim/milvus-cfg-slim-base.yaml b/deploy/milvus-slim/milvus-cfg-slim-base.yaml index 1efe0e51..03e2b906 100644 --- a/deploy/milvus-slim/milvus-cfg-slim-base.yaml +++ b/deploy/milvus-slim/milvus-cfg-slim-base.yaml @@ -17,11 +17,12 @@ # --- apiVersion: v1 -kind: ConfigMap +kind: Secret metadata: name: openserverless-milvus namespace: openserverless -data: +type: Opaque +stringData: default.yaml: |+ # Copyright (C) 2019-2021 Zilliz. All rights reserved. # @@ -38,7 +39,7 @@ data: rootPath: milvus auth: enabled: true - userName: root + userName: etcdmilvus password: 0therPa55 endpoints: - openserverless-etcd-0.openserverless-etcd-headless.openserverless.svc.cluster.local:2379 @@ -47,7 +48,7 @@ data: type: etcd minio: - address: openserverless-minio + address: seaweedfs port: 9000 accessKeyID: {{milvus_s3_username}} secretAccessKey: {{milvus_s3_password}} diff --git a/deploy/milvus-slim/milvus-s3-secret.yaml b/deploy/milvus-slim/milvus-s3-secret.yaml index b1f61d93..233f8906 100644 --- a/deploy/milvus-slim/milvus-s3-secret.yaml +++ b/deploy/milvus-slim/milvus-s3-secret.yaml @@ -23,8 +23,8 @@ metadata: name: openserverless-milvus-s3-secret namespace: openserverless stringData: - accesskey: miniomilvus - secretkey: miniomilvuspwd + accesskey: seaweedfsmilvus + secretkey: seaweedfsmilvuspwd diff --git a/deploy/milvus-slim/milvus-svc.yaml b/deploy/milvus-slim/milvus-svc.yaml index 30e687ae..e3f35a17 100644 --- a/deploy/milvus-slim/milvus-svc.yaml +++ b/deploy/milvus-slim/milvus-svc.yaml @@ -26,17 +26,15 @@ metadata: app.kubernetes.io/instance: openserverless-milvus component: "standalone" spec: - type: NodePort + type: ClusterIP ports: - name: milvus port: 19530 - nodePort: 32530 protocol: TCP targetPort: milvus - name: metrics protocol: TCP port: 9091 - nodePort: 32531 targetPort: metrics selector: app.kubernetes.io/name: milvus diff --git a/deploy/milvus-slim/milvus.yaml b/deploy/milvus-slim/milvus.yaml index fe68a8eb..34adb68b 100644 --- a/deploy/milvus-slim/milvus.yaml +++ b/deploy/milvus-slim/milvus.yaml @@ -101,8 +101,9 @@ spec: - emptyDir: {} name: tools - name: milvus-config - configMap: - name: openserverless-milvus + secret: + secretName: openserverless-milvus + defaultMode: 0440 - name: milvus-data-disk persistentVolumeClaim: claimName: openserverless-milvus diff --git a/deploy/milvus/milvus-cfg-base.yaml b/deploy/milvus/milvus-cfg-base.yaml index 9041ef35..ab2c91e0 100644 --- a/deploy/milvus/milvus-cfg-base.yaml +++ b/deploy/milvus/milvus-cfg-base.yaml @@ -17,11 +17,12 @@ # --- apiVersion: v1 -kind: ConfigMap +kind: Secret metadata: name: openserverless-milvus namespace: openserverless -data: +type: Opaque +stringData: default.yaml: |+ # Copyright (C) 2019-2021 Zilliz. All rights reserved. # @@ -38,7 +39,7 @@ data: rootPath: milvus auth: enabled: true - userName: root + userName: etcdmilvus password: 0therPa55 endpoints: - openserverless-etcd-0.openserverless-etcd-headless.openserverless.svc.cluster.local:2379 @@ -47,9 +48,9 @@ data: type: etcd minio: - address: openserverless-minio + address: seaweedfs port: 9000 - accessKeyID: miniomilvus + accessKeyID: seaweedfsmilvus secretAccessKey: s0meP@ass3 useSSL: false bucketName: vectors diff --git a/deploy/milvus/milvus-cfg-deps.yaml b/deploy/milvus/milvus-cfg-deps.yaml index c17fe732..3fe4a254 100644 --- a/deploy/milvus/milvus-cfg-deps.yaml +++ b/deploy/milvus/milvus-cfg-deps.yaml @@ -18,105 +18,6 @@ --- apiVersion: v1 kind: ConfigMap -metadata: - name: openserverless-milvus-minio -data: - initialize: |- - #!/bin/sh - set -e ; # Have script exit in the event of a failed command. - MC_CONFIG_DIR="/etc/minio/mc/" - MC="/usr/bin/mc --insecure --config-dir ${MC_CONFIG_DIR}" - - # connectToMinio - # Use a check-sleep-check loop to wait for Minio service to be available - connectToMinio() { - SCHEME=$1 - ATTEMPTS=0 ; LIMIT=29 ; # Allow 30 attempts - set -e ; # fail if we can't read the keys. - ACCESS=$(cat /config/accesskey) ; SECRET=$(cat /config/secretkey) ; - set +e ; # The connections to minio are allowed to fail. - echo "Connecting to Minio server: $SCHEME://$MINIO_ENDPOINT:$MINIO_PORT" ; - MC_COMMAND="${MC} config host add myminio $SCHEME://$MINIO_ENDPOINT:$MINIO_PORT $ACCESS $SECRET" ; - $MC_COMMAND ; - STATUS=$? ; - until [ $STATUS = 0 ] - do - ATTEMPTS=`expr $ATTEMPTS + 1` ; - echo \"Failed attempts: $ATTEMPTS\" ; - if [ $ATTEMPTS -gt $LIMIT ]; then - exit 1 ; - fi ; - sleep 2 ; # 1 second intervals between attempts - $MC_COMMAND ; - STATUS=$? ; - done ; - set -e ; # reset `e` as active - return 0 - } - - # checkBucketExists ($bucket) - # Check if the bucket exists, by using the exit code of `mc ls` - checkBucketExists() { - BUCKET=$1 - CMD=$(${MC} ls myminio/$BUCKET > /dev/null 2>&1) - return $? - } - - # createBucket ($bucket, $policy, $purge) - # Ensure bucket exists, purging if asked to - createBucket() { - BUCKET=$1 - POLICY=$2 - PURGE=$3 - VERSIONING=$4 - - # Purge the bucket, if set & exists - # Since PURGE is user input, check explicitly for `true` - if [ $PURGE = true ]; then - if checkBucketExists $BUCKET ; then - echo "Purging bucket '$BUCKET'." - set +e ; # don't exit if this fails - ${MC} rm -r --force myminio/$BUCKET - set -e ; # reset `e` as active - else - echo "Bucket '$BUCKET' does not exist, skipping purge." - fi - fi - - # Create the bucket if it does not exist - if ! checkBucketExists $BUCKET ; then - echo "Creating bucket '$BUCKET'" - ${MC} mb myminio/$BUCKET - else - echo "Bucket '$BUCKET' already exists." - fi - - - # set versioning for bucket - if [ ! -z $VERSIONING ] ; then - if [ $VERSIONING = true ] ; then - echo "Enabling versioning for '$BUCKET'" - ${MC} version enable myminio/$BUCKET - elif [ $VERSIONING = false ] ; then - echo "Suspending versioning for '$BUCKET'" - ${MC} version suspend myminio/$BUCKET - fi - else - echo "Bucket '$BUCKET' versioning unchanged." - fi - - # At this point, the bucket should exist, skip checking for existence - # Set policy on the bucket - echo "Setting policy of bucket '$BUCKET' to '$POLICY'." - ${MC} policy set $POLICY myminio/$BUCKET - } - - # Try connecting to Minio instance - scheme=http - connectToMinio $scheme ---- -apiVersion: v1 -kind: ConfigMap metadata: name: "openserverless-milvus-pulsarv3-bookie" namespace: openserverless diff --git a/deploy/milvus/milvus-s3-secret.yaml b/deploy/milvus/milvus-s3-secret.yaml index b1f61d93..233f8906 100644 --- a/deploy/milvus/milvus-s3-secret.yaml +++ b/deploy/milvus/milvus-s3-secret.yaml @@ -23,8 +23,8 @@ metadata: name: openserverless-milvus-s3-secret namespace: openserverless stringData: - accesskey: miniomilvus - secretkey: miniomilvuspwd + accesskey: seaweedfsmilvus + secretkey: seaweedfsmilvuspwd diff --git a/deploy/milvus/milvus-svc.yaml b/deploy/milvus/milvus-svc.yaml index 65143f6b..637fa0e5 100644 --- a/deploy/milvus/milvus-svc.yaml +++ b/deploy/milvus/milvus-svc.yaml @@ -69,17 +69,15 @@ metadata: app.kubernetes.io/instance: openserverless-milvus component: "standalone" spec: - type: NodePort + type: ClusterIP ports: - name: milvus port: 19530 - nodePort: 32530 protocol: TCP targetPort: milvus - name: metrics protocol: TCP port: 9091 - nodePort: 32531 targetPort: metrics selector: app.kubernetes.io/name: milvus diff --git a/deploy/milvus/milvus.yaml b/deploy/milvus/milvus.yaml index fe68a8eb..34adb68b 100644 --- a/deploy/milvus/milvus.yaml +++ b/deploy/milvus/milvus.yaml @@ -101,8 +101,9 @@ spec: - emptyDir: {} name: tools - name: milvus-config - configMap: - name: openserverless-milvus + secret: + secretName: openserverless-milvus + defaultMode: 0440 - name: milvus-data-disk persistentVolumeClaim: claimName: openserverless-milvus diff --git a/deploy/minio/00-minio-pvc.yaml b/deploy/minio/00-minio-pvc.yaml deleted file mode 100644 index 1a69bfd5..00000000 --- a/deploy/minio/00-minio-pvc.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: minio-pv-claim - namespace: openserverless - labels: - app: minio-storage-claim -spec: - accessModes: - - ReadWriteOnce - storageClassName: standard - resources: - requests: - storage: 1Gi \ No newline at end of file diff --git a/deploy/minio/01-minio-dep.yaml b/deploy/minio/01-minio-dep.yaml deleted file mode 100644 index 6afe15f4..00000000 --- a/deploy/minio/01-minio-dep.yaml +++ /dev/null @@ -1,62 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -apiVersion: apps/v1 # for k8s versions before 1.9.0 use apps/v1beta2 and before 1.8.0 use extensions/v1beta1 -kind: Deployment -metadata: - name: minio-deployment - namespace: openserverless -spec: - selector: - matchLabels: - app: minio - strategy: - type: Recreate - template: - metadata: - labels: - app: minio - name: minio - annotations: - whisks.openserverless.org/annotate-version: "true" - spec: - volumes: - - name: data - persistentVolumeClaim: - claimName: minio-pv-claim - containers: - - name: minio - image: bitnami/minio:2023.3.24 - command: - - /bin/bash - - -c - args: - - minio server /data --console-address :9090 - env: - - name: MINIO_ROOT_USER - value: minio - - name: MINIO_ROOT_PASSWORD - value: minio123 - ports: - - name: api - containerPort: 9000 - - name: content - containerPort: 9090 - volumeMounts: - - name: data # must match the volume name, above - mountPath: "/data" \ No newline at end of file diff --git a/deploy/minio/02-minio-svc.yaml b/deploy/minio/02-minio-svc.yaml deleted file mode 100644 index 133aad4c..00000000 --- a/deploy/minio/02-minio-svc.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -apiVersion: v1 -kind: Service -metadata: - name: openserverless-minio -spec: - type: NodePort - ports: - - port: 9000 - targetPort: 9000 - nodePort: 32090 - protocol: TCP - name: minio-api - - port: 9090 - targetPort: 9090 - nodePort: 32091 - protocol: TCP - name: minio-console - selector: - app: minio \ No newline at end of file diff --git a/deploy/mongodb-operator-deploy/mongodb-auth-openserverless.yaml b/deploy/mongodb-operator-deploy/mongodb-auth-openserverless.yaml deleted file mode 100644 index 7e354224..00000000 --- a/deploy/mongodb-operator-deploy/mongodb-auth-openserverless.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. ---- -apiVersion: v1 -kind: Secret -metadata: - name: mongodb-openserverless-auth - namespace: openserverless -type: Opaque -stringData: - mongodb_openserverless_psw: s3cr3t \ No newline at end of file diff --git a/deploy/mongodb-operator-deploy/mongodb-auth.yaml b/deploy/mongodb-operator-deploy/mongodb-auth.yaml deleted file mode 100644 index 712a9d4f..00000000 --- a/deploy/mongodb-operator-deploy/mongodb-auth.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. ---- -apiVersion: v1 -kind: Secret -metadata: - name: mongodb-admin-auth - namespace: openserverless -type: Opaque -stringData: - mongodb_admin_psw: sUpErs3cr3t \ No newline at end of file diff --git a/deploy/mongodb-operator-deploy/mongodb-svc.yaml b/deploy/mongodb-operator-deploy/mongodb-svc.yaml deleted file mode 100644 index 73219bc2..00000000 --- a/deploy/mongodb-operator-deploy/mongodb-svc.yaml +++ /dev/null @@ -1,34 +0,0 @@ ---- -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -apiVersion: v1 -kind: Service -metadata: - name: mongodb - namespace: openserverless -spec: - type: NodePort - publishNotReadyAddresses: true - externalTrafficPolicy: Local - sessionAffinity: None - ports: - - port: 27017 - targetPort: 27017 - nodePort: 32717 - selector: - app: openserverless-mongodb-svc - statefulset.kubernetes.io/pod-name: openserverless-mongodb-0 \ No newline at end of file diff --git a/deploy/mongodb-operator-deploy/mongodb.yaml b/deploy/mongodb-operator-deploy/mongodb.yaml deleted file mode 100644 index 3b2a8102..00000000 --- a/deploy/mongodb-operator-deploy/mongodb.yaml +++ /dev/null @@ -1,52 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. ---- -apiVersion: mongodbcommunity.mongodb.com/v1 -kind: MongoDBCommunity -metadata: - name: openserverless-mongodb - namespace: openserverless -spec: - members: 1 - type: ReplicaSet - version: "4.2.6" - security: - authentication: - modes: ["SCRAM"] - users: - - name: admin - db: admin - passwordSecretRef: # a reference to the secret that will be used to generate the user's password - name: mongodb-admin-auth - key: mongodb_admin_psw - roles: - - name: clusterAdmin - db: admin - - name: userAdminAnyDatabase - db: admin - scramCredentialsSecretName: mongodb-admin-scram - - name: openserverless - db: openserverless - passwordSecretRef: # a reference to the secret that will be used to generate the user's password - name: mongodb-openserverless-auth - key: mongodb_openserverless_psw - roles: - - name: readWrite - db: openserverless - scramCredentialsSecretName: mongodb-openserverless-scram - additionalMongodConfig: - storage.wiredTiger.engineConfig.journalCompressor: zlib \ No newline at end of file diff --git a/deploy/mongodb-operator/mongodb-crd.yaml b/deploy/mongodb-operator/mongodb-crd.yaml deleted file mode 100644 index 678becd7..00000000 --- a/deploy/mongodb-operator/mongodb-crd.yaml +++ /dev/null @@ -1,451 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - service.binding/type: 'mongodb' - service.binding/provider: 'community' - service.binding: 'path={.metadata.name}-{.spec.users[0].db}-{.spec.users[0].name},objectType=Secret' - service.binding/connectionString: 'path={.metadata.name}-{.spec.users[0].db}-{.spec.users[0].name},objectType=Secret,sourceKey=connectionString.standardSrv' - service.binding/username: 'path={.metadata.name}-{.spec.users[0].db}-{.spec.users[0].name},objectType=Secret,sourceKey=username' - service.binding/password: 'path={.metadata.name}-{.spec.users[0].db}-{.spec.users[0].name},objectType=Secret,sourceKey=password' - creationTimestamp: null - name: mongodbcommunity.mongodbcommunity.mongodb.com -spec: - group: mongodbcommunity.mongodb.com - names: - kind: MongoDBCommunity - listKind: MongoDBCommunityList - plural: mongodbcommunity - shortNames: - - mdbc - singular: mongodbcommunity - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: Current state of the MongoDB deployment - jsonPath: .status.phase - name: Phase - type: string - - description: Version of MongoDB server - jsonPath: .status.version - name: Version - type: string - name: v1 - schema: - openAPIV3Schema: - description: MongoDBCommunity is the Schema for the mongodbs 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: MongoDBCommunitySpec defines the desired state of MongoDB - properties: - additionalMongodConfig: - description: 'AdditionalMongodConfig is additional configuration that - can be passed to each data-bearing mongod at runtime. Uses the same - structure as the mongod configuration file: https://docs.mongodb.com/manual/reference/configuration-options/' - nullable: true - type: object - x-kubernetes-preserve-unknown-fields: true - arbiters: - description: 'Arbiters is the number of arbiters to add to the Replica - Set. It is not recommended to have more than one arbiter per Replica - Set. More info: https://docs.mongodb.com/manual/tutorial/add-replica-set-arbiter/' - type: integer - automationConfig: - description: AutomationConfigOverride is merged on top of the operator - created automation config. Processes are merged by name. Currently - Only the process.disabled field is supported. - properties: - processes: - items: - description: OverrideProcess contains fields that we can override - on the AutomationConfig processes. - properties: - disabled: - type: boolean - name: - type: string - required: - - disabled - - name - type: object - type: array - required: - - processes - type: object - featureCompatibilityVersion: - description: FeatureCompatibilityVersion configures the feature compatibility - version that will be set for the deployment - type: string - members: - description: Members is the number of members in the replica set - type: integer - prometheus: - description: Prometheus configurations. - properties: - metricsPath: - description: Indicates path to the metrics endpoint. - pattern: ^\/[a-z0-9]+$ - type: string - passwordSecretRef: - description: Name of a Secret containing a HTTP Basic Auth Password. - properties: - key: - description: Key is the key in the secret storing this password. - Defaults to "password" - type: string - name: - description: Name is the name of the secret storing this user's - password - type: string - required: - - name - type: object - port: - description: Port where metrics endpoint will bind to. Defaults - to 9216. - type: integer - tlsSecretKeyRef: - description: Name of a Secret (type kubernetes.io/tls) holding - the certificates to use in the Prometheus endpoint. - properties: - key: - description: Key is the key in the secret storing this password. - Defaults to "password" - type: string - name: - description: Name is the name of the secret storing this user's - password - type: string - required: - - name - type: object - username: - description: HTTP Basic Auth Username for metrics endpoint. - type: string - required: - - passwordSecretRef - - username - type: object - replicaSetHorizons: - description: ReplicaSetHorizons Add this parameter and values if you - need your database to be accessed outside of Kubernetes. This setting - allows you to provide different DNS settings within the Kubernetes - cluster and to the Kubernetes cluster. The Kubernetes Operator uses - split horizon DNS for replica set members. This feature allows communication - both within the Kubernetes cluster and from outside Kubernetes. - items: - additionalProperties: - type: string - type: object - type: array - security: - description: Security configures security features, such as TLS, and - authentication settings for a deployment - properties: - authentication: - properties: - ignoreUnknownUsers: - default: true - nullable: true - type: boolean - modes: - description: Modes is an array specifying which authentication - methods should be enabled. - items: - enum: - - SCRAM - - SCRAM-SHA-256 - - SCRAM-SHA-1 - type: string - type: array - required: - - modes - type: object - roles: - description: User-specified custom MongoDB roles that should be - configured in the deployment. - items: - description: CustomRole defines a custom MongoDB role. - properties: - authenticationRestrictions: - description: The authentication restrictions the server - enforces on the role. - items: - description: AuthenticationRestriction specifies a list - of IP addresses and CIDR ranges users are allowed to - connect to or from. - properties: - clientSource: - items: - type: string - type: array - serverAddress: - items: - type: string - type: array - required: - - clientSource - - serverAddress - type: object - type: array - db: - description: The database of the role. - type: string - privileges: - description: The privileges to grant the role. - items: - description: Privilege defines the actions a role is allowed - to perform on a given resource. - properties: - actions: - items: - type: string - type: array - resource: - description: Resource specifies specifies the resources - upon which a privilege permits actions. See https://docs.mongodb.com/manual/reference/resource-document - for more. - properties: - anyResource: - type: boolean - cluster: - type: boolean - collection: - type: string - db: - type: string - type: object - required: - - actions - - resource - type: object - type: array - role: - description: The name of the role. - type: string - roles: - description: An array of roles from which this role inherits - privileges. - items: - description: Role is the database role this user should - have - properties: - db: - description: DB is the database the role can act on - type: string - name: - description: Name is the name of the role - type: string - required: - - db - - name - type: object - type: array - required: - - db - - privileges - - role - type: object - type: array - tls: - description: TLS configuration for both client-server and server-server - communication - properties: - caCertificateSecretRef: - description: CaCertificateSecret is a reference to a Secret - containing the certificate for the CA which signed the server - certificates The certificate is expected to be available - under the key "ca.crt" - properties: - name: - type: string - required: - - name - type: object - caConfigMapRef: - description: CaConfigMap is a reference to a ConfigMap containing - the certificate for the CA which signed the server certificates - The certificate is expected to be available under the key - "ca.crt" This field is ignored when CaCertificateSecretRef - is configured - properties: - name: - type: string - required: - - name - type: object - certificateKeySecretRef: - description: CertificateKeySecret is a reference to a Secret - containing a private key and certificate to use for TLS. - The key and cert are expected to be PEM encoded and available - at "tls.key" and "tls.crt". This is the same format used - for the standard "kubernetes.io/tls" Secret type, but no - specific type is required. Alternatively, an entry tls.pem, - containing the concatenation of cert and key, can be provided. - If all of tls.pem, tls.crt and tls.key are present, the - tls.pem one needs to be equal to the concatenation of tls.crt - and tls.key - properties: - name: - type: string - required: - - name - type: object - enabled: - type: boolean - optional: - description: Optional configures if TLS should be required - or optional for connections - type: boolean - required: - - enabled - type: object - type: object - statefulSet: - description: StatefulSetConfiguration holds the optional custom StatefulSet - that should be merged into the operator created one. - properties: - spec: - type: object - x-kubernetes-preserve-unknown-fields: true - required: - - spec - type: object - type: - description: Type defines which type of MongoDB deployment the resource - should create - enum: - - ReplicaSet - type: string - users: - description: Users specifies the MongoDB users that should be configured - in your deployment - items: - properties: - db: - description: DB is the database the user is stored in. Defaults - to "admin" - type: string - name: - description: Name is the username of the user - type: string - passwordSecretRef: - description: PasswordSecretRef is a reference to the secret - containing this user's password - properties: - key: - description: Key is the key in the secret storing this password. - Defaults to "password" - type: string - name: - description: Name is the name of the secret storing this - user's password - type: string - required: - - name - type: object - roles: - description: Roles is an array of roles assigned to this user - items: - description: Role is the database role this user should have - properties: - db: - description: DB is the database the role can act on - type: string - name: - description: Name is the name of the role - type: string - required: - - db - - name - type: object - type: array - scramCredentialsSecretName: - description: ScramCredentialsSecretName appended by string "scram-credentials" - is the name of the secret object created by the mongoDB operator - for storing SCRAM credentials These secrets names must be - different for each user in a deployment. - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - - passwordSecretRef - - roles - - scramCredentialsSecretName - type: object - type: array - version: - description: Version defines which version of MongoDB will be used - type: string - required: - - security - - type - - users - type: object - status: - description: MongoDBCommunityStatus defines the observed state of MongoDB - properties: - currentMongoDBArbiters: - type: integer - currentMongoDBMembers: - type: integer - currentStatefulSetArbitersReplicas: - type: integer - currentStatefulSetReplicas: - type: integer - message: - type: string - mongoUri: - type: string - phase: - type: string - version: - type: string - required: - - currentMongoDBMembers - - currentStatefulSetReplicas - - mongoUri - - phase - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/deploy/mongodb-operator/mongodb-operator.yaml b/deploy/mongodb-operator/mongodb-operator.yaml deleted file mode 100644 index 0e932ecc..00000000 --- a/deploy/mongodb-operator/mongodb-operator.yaml +++ /dev/null @@ -1,92 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -apiVersion: apps/v1 -kind: Deployment -metadata: - annotations: - email: support@mongodb.com - labels: - owner: mongodb - name: mongodb-kubernetes-operator - namespace: openserverless -spec: - replicas: 1 - selector: - matchLabels: - name: mongodb-kubernetes-operator - strategy: - rollingUpdate: - maxUnavailable: 1 - type: RollingUpdate - template: - metadata: - labels: - name: mongodb-kubernetes-operator - spec: - affinity: - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchExpressions: - - key: name - operator: In - values: - - mongodb-kubernetes-operator - topologyKey: kubernetes.io/hostname - containers: - - command: - - /usr/local/bin/entrypoint - env: - - name: WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: OPERATOR_NAME - value: mongodb-kubernetes-operator - - name: AGENT_IMAGE - value: quay.io/mongodb/mongodb-agent:12.0.15.7646-1 - - name: VERSION_UPGRADE_HOOK_IMAGE - value: quay.io/mongodb/mongodb-kubernetes-operator-version-upgrade-post-start-hook:1.0.6 - - name: READINESS_PROBE_IMAGE - value: quay.io/mongodb/mongodb-kubernetes-readinessprobe:1.0.12 - - name: MONGODB_IMAGE - value: mongo - - name: MONGODB_REPO_URL - value: docker.io - image: quay.io/mongodb/mongodb-kubernetes-operator:0.7.8 - imagePullPolicy: IfNotPresent - name: mongodb-kubernetes-operator - resources: - limits: - cpu: 1100m - memory: 1Gi - requests: - cpu: 500m - memory: 200Mi - securityContext: - readOnlyRootFilesystem: true - runAsUser: 2000 - allowPrivilegeEscalation: false - securityContext: - seccompProfile: - type: RuntimeDefault - serviceAccountName: mongodb-kubernetes-operator \ No newline at end of file diff --git a/deploy/mongodb-operator/mongodb-rbac.yaml b/deploy/mongodb-operator/mongodb-rbac.yaml deleted file mode 100644 index bbf758ce..00000000 --- a/deploy/mongodb-operator/mongodb-rbac.yaml +++ /dev/null @@ -1,123 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: mongodb-kubernetes-operator - namespace: openserverless - ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: mongodb-database - namespace: openserverless ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: mongodb-kubernetes-operator - namespace: openserverless -rules: -- apiGroups: - - "" - resources: - - pods - - services - - configmaps - - secrets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - apps - resources: - - statefulsets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mongodbcommunity.mongodb.com - resources: - - mongodbcommunity - - mongodbcommunity/status - - mongodbcommunity/spec - - mongodbcommunity/finalizers - verbs: - - get - - patch - - list - - update - - watch ---- -kind: Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: mongodb-database -rules: - - apiGroups: - - "" - resources: - - secrets - verbs: - - get - - apiGroups: - - "" - resources: - - pods - verbs: - - patch - - delete - - get ---- -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: mongodb-kubernetes-operator - namespace: openserverless -subjects: -- kind: ServiceAccount - name: mongodb-kubernetes-operator -roleRef: - kind: Role - name: mongodb-kubernetes-operator - apiGroup: rbac.authorization.k8s.io ---- -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: mongodb-database - namespace: openserverless -subjects: -- kind: ServiceAccount - name: mongodb-database -roleRef: - kind: Role - name: mongodb-database - apiGroup: rbac.authorization.k8s.io diff --git a/deploy/mongodb-standalone/mongodb-auth.yaml b/deploy/mongodb-standalone/mongodb-auth.yaml deleted file mode 100644 index 712a9d4f..00000000 --- a/deploy/mongodb-standalone/mongodb-auth.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. ---- -apiVersion: v1 -kind: Secret -metadata: - name: mongodb-admin-auth - namespace: openserverless -type: Opaque -stringData: - mongodb_admin_psw: sUpErs3cr3t \ No newline at end of file diff --git a/deploy/mongodb-standalone/mongodb-cm.yaml b/deploy/mongodb-standalone/mongodb-cm.yaml deleted file mode 100644 index be0f7c68..00000000 --- a/deploy/mongodb-standalone/mongodb-cm.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -apiVersion: v1 -kind: ConfigMap -metadata: - name: mongodb-cm - namespace: openserverless -data: - mongodb-js: |- - db = db.getSiblingDB('openserverless'); - - db.createUser({ - user: "openserverless", - pwd: "testpwd", - roles: [ - {role: "readWrite", db: "openserverless"} - ] - }); - - db.nuv_test_collection.insertOne({"message":"Welcome to openserverless!"}) \ No newline at end of file diff --git a/deploy/mongodb-standalone/mongodb-pvc.yaml b/deploy/mongodb-standalone/mongodb-pvc.yaml deleted file mode 100644 index 378feb87..00000000 --- a/deploy/mongodb-standalone/mongodb-pvc.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: mongodb-data - namespace: openserverless -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10Gi \ No newline at end of file diff --git a/deploy/mongodb-standalone/mongodb-sts.yaml b/deploy/mongodb-standalone/mongodb-sts.yaml deleted file mode 100644 index 6b4a3a5a..00000000 --- a/deploy/mongodb-standalone/mongodb-sts.yaml +++ /dev/null @@ -1,87 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: openserverless-mongodb - labels: - app: mongodb - namespace: openserverless -spec: - serviceName: openserverless-mongodb-svc - replicas: 1 - selector: - matchLabels: - app: openserverless-mongodb - template: - metadata: - labels: - app: openserverless-mongodb - name: openserverless-mongodb - spec: - containers: - - image: mongo:5.0.11 - name: mongodb - args: ["--dbpath","/data/db", "--bind_ip", "0.0.0.0"] - livenessProbe: - exec: - command: - - mongo - - --disableImplicitSessions - - --eval - - "db.adminCommand('ping')" - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 6 - readinessProbe: - exec: - command: - - mongo - - --disableImplicitSessions - - --eval - - "db.adminCommand('ping')" - initialDelaySeconds: 20 - periodSeconds: 10 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 6 - env: - - name: MONGO_INITDB_ROOT_USERNAME - value: whisk_admin - - name: MONGO_INITDB_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: mongodb-admin-auth - key: mongodb_admin_psw - volumeMounts: - - name: "mongodb-data-dir" - mountPath: "/data/db" - - name: "mongodb-init-dir" - mountPath: "/docker-entrypoint-initdb.d" - volumes: - - name: "mongodb-data-dir" - persistentVolumeClaim: - claimName: "mongodb-data" - - name: "mongodb-init-dir" - configMap: - name: mongodb-cm - items: - - key: mongodb-js - path: init-mongo.js \ No newline at end of file diff --git a/deploy/mongodb-standalone/mongodb-svc.yaml b/deploy/mongodb-standalone/mongodb-svc.yaml deleted file mode 100644 index 01c207b5..00000000 --- a/deploy/mongodb-standalone/mongodb-svc.yaml +++ /dev/null @@ -1,31 +0,0 @@ - -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. ---- -apiVersion: v1 -kind: Service -metadata: - name: openserverless-mongodb-svc - namespace: openserverless -spec: - clusterIP: None - ports: - - port: 27017 - targetPort: 27017 - selector: - app: openserverless-mongodb - statefulset.kubernetes.io/pod-name: openserverless-mongodb-0 \ No newline at end of file diff --git a/deploy/nginx-static/nginx-static-cm.yaml b/deploy/nginx-static/nginx-static-cm.yaml index 1edb12c0..5ac00341 100644 --- a/deploy/nginx-static/nginx-static-cm.yaml +++ b/deploy/nginx-static/nginx-static-cm.yaml @@ -46,14 +46,14 @@ data: #proxy_hide_header x-amz-server-side-encryption; #proxy_set_header Host $http_host; - proxy_pass http://minio.openserverless.svc.cluster.local:9000/; + proxy_pass http://seaweedfs.openserverless.svc.cluster.local:9000/; error_page 301 404 = @spa; proxy_intercept_errors on; } location @spa { rewrite /(.*?)/ /$1/index.html; - proxy_pass http://minio.openserverless.svc.cluster.local:9000; + proxy_pass http://seaweedfs.openserverless.svc.cluster.local:9000; error_page 404 = /404.html; proxy_intercept_errors on; } diff --git a/deploy/nginx-static/nginx-static-sts.yaml b/deploy/nginx-static/nginx-static-sts.yaml index 4fc2df7d..b607f360 100644 --- a/deploy/nginx-static/nginx-static-sts.yaml +++ b/deploy/nginx-static/nginx-static-sts.yaml @@ -34,9 +34,9 @@ spec: app: openserverless-static spec: initContainers: - - name: check-minio + - name: check-seaweedfs image: busybox:1.36.0 - command: ['sh', '-c', "until nslookup openserverless-minio.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for minio; sleep 2; done"] + command: ['sh', '-c', "until nslookup seaweedfs.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for seaweedfs; sleep 2; done"] containers: - name: openserverless-static image: nginxinc/nginx-unprivileged:1.24 diff --git a/deploy/ngrok b/deploy/ngrok deleted file mode 100644 index e69de29b..00000000 diff --git a/deploy/openserverless-operator/whisk.yaml b/deploy/openserverless-operator/whisk.yaml index 906e176c..b769cb16 100644 --- a/deploy/openserverless-operator/whisk.yaml +++ b/deploy/openserverless-operator/whisk.yaml @@ -35,8 +35,6 @@ spec: mongodb: true # start redis redis: false - # start s3ninja - s3bucket: false # start the scheduler cron: true openwhisk: diff --git a/deploy/openserverless-permissions/operator-roles.yaml b/deploy/openserverless-permissions/operator-roles.yaml index 770d2244..7df96fec 100644 --- a/deploy/openserverless-permissions/operator-roles.yaml +++ b/deploy/openserverless-permissions/operator-roles.yaml @@ -101,6 +101,11 @@ rules: resources: ["milvus","milvus/status","milvus/finalizers"] verbs: ["get","patch","list","update","watch","create","delete"] +# Datastore ingress isolation is reconciled by the operator. +- apiGroups: ["networking.k8s.io"] + resources: ["networkpolicies"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] + --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 diff --git a/deploy/openserverless-permissions/whisk-crd.yaml b/deploy/openserverless-permissions/whisk-crd.yaml index 17af9b44..dacd2c6c 100644 --- a/deploy/openserverless-permissions/whisk-crd.yaml +++ b/deploy/openserverless-permissions/whisk-crd.yaml @@ -117,11 +117,8 @@ spec: tls: description: enables TLS support via let's encrypt certificate for the ow apihost type: boolean - minio: - description: deploys MINIO as storage - type: boolean static: - description: activate the MINIO/CEPH OBJECT STORE static internal service (requires components.minio or components.cephobjectstore to be true) + description: activate the SEAWEEDFS OBJECT STORE static internal service (requires components.seaweedfs to be true) type: boolean zookeeper: description: deploys zookeeper if supported by the operator @@ -216,47 +213,15 @@ spec: - admin - controller mongodb: - description: used to configure a openserverless shared mongodb instance + description: used to configure the openserverless shared mongodb-compatible instance (served by FerretDB) type: object + x-kubernetes-preserve-unknown-fields: true properties: - host: - description: mongodb hostname - type: string volume-size: description: mongodb volume size in GB type: integer - exposedExternally: - type: boolean - useOperator: - description: boolean flag used to deploy mongodb using teh community operator. It default to false - type: boolean - admin: - description: mongodb admin suer credentials - type: object - properties: - user: - type: string - password: - type: string - required: - - user - - password - openserverless: - description: mongodb openserverless database credentials - type: object - properties: - user: - type: string - password: - type: string - required: - - user - - password required: - - host - volume-size - - admin - - openserverless postgres: description: used to configure a openserverless shared postgres instance type: object @@ -363,58 +328,6 @@ spec: required: - acme-registered-email - acme-server-url - minio: - description: used to configure the internal minio data storage service - type: object - properties: - volume-size: - description: volume size in GB - type: integer - admin: - description: used to configure the MINIO admin user - type: object - properties: - user: - type: string - password: - type: string - required: - - user - - password - openserverless: - description: used to configure the MINIO openserverless user used for non administrative purposes - type: object - properties: - user: - type: string - password: - type: string - required: - - user - - password - ingress: - description: configuration option for global minio ingresses exposure - type: object - properties: - s3-enabled: - description: boolean flag to activate MINIO S3 compatible ingress. Default to false - type: boolean - s3-hostname: - description: ingress hostname to be used if true (normally s3.) - type: string - console-enabled: - description: boolean flag to expose MINIO console - type: boolean - console-hostname: - description: ingress hostname to be used (nora=mally minio.) - type: string - required: - - s3-enabled - - console-enabled - required: - - volume-size - - admin - - openserverless redis: description: used to configure redis default credentials type: object @@ -781,7 +694,7 @@ spec: description: ETCD milvus user password type: string s3: - description: MINIO/S3 milvus user password + description: SEAWEEDFS/S3 milvus user password type: string required: - root @@ -845,7 +758,7 @@ spec: description: default bucket quota, default to 1024MB type: integer openserverless: - description: used to configure the MINIO openserverless user used for non administrative purposes + description: used to configure the SEAWEEDFS openserverless user used for non administrative purposes type: object properties: user: @@ -856,7 +769,7 @@ spec: - user - password ingress: - description: configuration option for global minio ingresses exposure + description: configuration option for global seaweedfs ingresses exposure type: object properties: s3-enabled: @@ -900,8 +813,6 @@ spec: # type: string # tls: # type: string - # minio: - # type: string # postgres: # type: string additionalPrinterColumns: @@ -950,11 +861,6 @@ spec: priority: 0 jsonPath: .status.whisk_create.tls description: Tls - - name: Minio - type: string - priority: 0 - jsonPath: .status.whisk_create.minio - description: Minio - name: Postgres type: string priority: 0 diff --git a/deploy/openserverless-permissions/whisk-user-crd.yaml b/deploy/openserverless-permissions/whisk-user-crd.yaml index 0190ceb6..3a5d206c 100644 --- a/deploy/openserverless-permissions/whisk-user-crd.yaml +++ b/deploy/openserverless-permissions/whisk-user-crd.yaml @@ -104,26 +104,26 @@ spec: - prefix - password object-storage: - description: used to setup openserverless MINIO instance with custom user resources + description: used to setup openserverless S3 (seaweedfs) instance with custom user resources type: object x-kubernetes-preserve-unknown-fields: true properties: password: - description: password to access the internal MINIO instance + description: password to access the internal S3 (seaweedfs) instance type: string quota: description: maximum quota assignable in mb to each defined bucket. Defaults to auto which will not set any specific quota type: string data: - description: used to configure the user MINIO dedicated buckets + description: used to configure the user S3 dedicated buckets type: object x-kubernetes-preserve-unknown-fields: true properties: enabled: - description: boolean flag to configure a MINIO data bucket for the user. defaulted to false + description: boolean flag to configure an S3 data bucket for the user. defaulted to false type: boolean bucket: - description: MINIO data bucket name + description: S3 data bucket name type: string x-kubernetes-validations: - rule: "self.matches('^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$')" @@ -132,7 +132,7 @@ spec: - enabled - bucket route: - description: used to configure a user MINIO static web content provider bucket + description: used to configure a user S3 static web content provider bucket type: object x-kubernetes-preserve-unknown-fields: true properties: diff --git a/deploy/qdrant/qdrant-cm.yaml b/deploy/qdrant/qdrant-cm.yaml deleted file mode 100644 index c1d39e76..00000000 --- a/deploy/qdrant/qdrant-cm.yaml +++ /dev/null @@ -1,47 +0,0 @@ -# Source: openwhisk/templates/couchdb-pod.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: openserverless-qdrant - labels: - helm.sh/chart: qdrant-0.9.2 - app: qdrant - app.kubernetes.io/name: qdrant - app.kubernetes.io/instance: openserverless-qdrant - app.kubernetes.io/version: "v1.9.5" - app.kubernetes.io/managed-by: Helm -data: - initialize.sh: | - #!/bin/sh - SET_INDEX=${HOSTNAME##*-} - echo "Starting initializing for pod $SET_INDEX" - if [ "$SET_INDEX" = "0" ]; then - exec ./entrypoint.sh --uri 'http://openserverless-qdrant-0.openserverless-qdrant-headless:6335' - else - exec ./entrypoint.sh --bootstrap 'http://openserverless-qdrant-0.openserverless-qdrant-headless:6335' --uri 'http://openserverless-qdrant-'"$SET_INDEX"'.openserverless-qdrant-headless:6335' - fi - - production.yaml: | - cluster: - consensus: - tick_period_ms: 100 - enabled: true - p2p: - port: 6335 \ No newline at end of file diff --git a/deploy/qdrant/qdrant-sts.yaml b/deploy/qdrant/qdrant-sts.yaml deleted file mode 100644 index a56f9aee..00000000 --- a/deploy/qdrant/qdrant-sts.yaml +++ /dev/null @@ -1,141 +0,0 @@ -# Source: openwhisk/templates/couchdb-pod.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: openserverless-qdrant - labels: - helm.sh/chart: qdrant-0.9.2 - app: qdrant - app.kubernetes.io/name: qdrant - app.kubernetes.io/instance: openserverless-qdrant - app.kubernetes.io/version: "v1.9.5" - app.kubernetes.io/managed-by: Helm -spec: - replicas: 2 - selector: - matchLabels: - app: qdrant - app.kubernetes.io/name: qdrant - app.kubernetes.io/instance: openserverless-qdrant - serviceName: openserverless-qdrant-headless - template: - metadata: - annotations: - checksum/config: 33ac3ce10060649fc5af9fd81289a09a645ecb5547ff9a7f14468923c717c94d - labels: - app: qdrant - app.kubernetes.io/name: qdrant - app.kubernetes.io/instance: openserverless-qdrant - spec: - initContainers: - - name: ensure-dir-ownership - image: "docker.io/qdrant/qdrant:v1.9.5" - command: - - chown - - -R - - 1000:3000 - - /qdrant/storage - - /qdrant/snapshots - volumeMounts: - - name: openserverless-qdrant-storage - mountPath: /qdrant/storage - - name: qdrant-snapshots - mountPath: /qdrant/snapshots - containers: - - name: qdrant - image: "docker.io/qdrant/qdrant:v1.9.5" - imagePullPolicy: IfNotPresent - env: - - name: QDRANT_INIT_FILE_PATH - value: /qdrant/init/.qdrant-initialized - command: ["/bin/bash", "-c"] - args: - - ./config/initialize.sh - ports: - - name: http - containerPort: 6333 - protocol: TCP - - name: grpc - containerPort: 6334 - protocol: TCP - - name: p2p - containerPort: 6335 - protocol: TCP - readinessProbe: - httpGet: - path: "/readyz" - port: 6333 - initialDelaySeconds: 5 - timeoutSeconds: 1 - periodSeconds: 5 - successThreshold: 1 - failureThreshold: 6 - resources: - {} - securityContext: - allowPrivilegeEscalation: false - privileged: false - readOnlyRootFilesystem: true - runAsGroup: 2000 - runAsNonRoot: true - runAsUser: 1000 - lifecycle: - preStop: - exec: - command: - - sleep - - "3" - volumeMounts: - - name: openserverless-qdrant-storage - mountPath: /qdrant/storage - - name: qdrant-config - mountPath: /qdrant/config/initialize.sh - subPath: initialize.sh - - name: qdrant-config - mountPath: /qdrant/config/production.yaml - subPath: production.yaml - - name: qdrant-snapshots - mountPath: /qdrant/snapshots - - name: qdrant-init - mountPath: /qdrant/init - securityContext: - fsGroup: 3000 - fsGroupChangePolicy: Always - volumes: - - name: qdrant-config - configMap: - name: openserverless-qdrant - defaultMode: 0755 - - name: qdrant-snapshots - emptyDir: {} - - name: qdrant-init - emptyDir: {} - volumeClaimTemplates: - - metadata: - name: openserverless-qdrant-storage - labels: - app: qdrant - spec: - storageClassName: - accessModes: - - "ReadWriteOnce" - resources: - requests: - storage: "10Gi" \ No newline at end of file diff --git a/deploy/qdrant/qdrant-svc.yaml b/deploy/qdrant/qdrant-svc.yaml deleted file mode 100644 index 4eebe02b..00000000 --- a/deploy/qdrant/qdrant-svc.yaml +++ /dev/null @@ -1,78 +0,0 @@ -# Source: openwhisk/templates/couchdb-pod.yaml -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ---- -apiVersion: v1 -kind: Service -metadata: - name: openserverless-qdrant-headless - labels: - helm.sh/chart: qdrant-0.9.2 - app: qdrant - app.kubernetes.io/name: qdrant - app.kubernetes.io/instance: openserverless-qdrant -spec: - clusterIP: None - publishNotReadyAddresses: true - ports: - - name: http - port: 6333 - targetPort: 6333 - protocol: TCP - - name: grpc - port: 6334 - targetPort: 6334 - protocol: TCP - - name: p2p - port: 6335 - targetPort: 6335 - protocol: TCP - selector: - app: qdrant - app.kubernetes.io/name: qdrant - app.kubernetes.io/instance: openserverless-qdrant ---- -apiVersion: v1 -kind: Service -metadata: - name: openserverless-qdrant - labels: - helm.sh/chart: qdrant-0.9.2 - app: qdrant - app.kubernetes.io/name: qdrant - app.kubernetes.io/instance: openserverless-qdrant - app.kubernetes.io/version: "v1.9.5" - app.kubernetes.io/managed-by: Helm -spec: - type: ClusterIP - ports: - - name: http - port: 6333 - targetPort: 6333 - protocol: TCP - - name: grpc - port: 6334 - targetPort: 6334 - protocol: TCP - - name: p2p - port: 6335 - targetPort: 6335 - protocol: TCP - selector: - app: qdrant - app.kubernetes.io/name: qdrant - app.kubernetes.io/instance: openserverless-qdrant \ No newline at end of file diff --git a/deploy/s3ninja/s3ninja-set.yaml b/deploy/s3ninja/s3ninja-set.yaml deleted file mode 100644 index 74c36f64..00000000 --- a/deploy/s3ninja/s3ninja-set.yaml +++ /dev/null @@ -1,45 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -apiVersion: apps/v1 -kind: Deployment -metadata: - name: s3ninja -spec: - replicas: 1 - strategy: - type: Recreate - selector: - matchLabels: - app.kubernetes.io/name: s3ninja - template: - metadata: - labels: - app.kubernetes.io/name: s3ninja - spec: - containers: - - name: s3ninja - image: scireum/s3-ninja - imagePullPolicy: IfNotPresent - ports: - - containerPort: 9000 - resources: - requests: - memory: 128Mi - cpu: 50m - limits: - memory: 256Mi - cpu: 250m diff --git a/deploy/s3ninja/s3ninja-svc.yaml b/deploy/s3ninja/s3ninja-svc.yaml deleted file mode 100644 index 2a86b294..00000000 --- a/deploy/s3ninja/s3ninja-svc.yaml +++ /dev/null @@ -1,30 +0,0 @@ - # Licensed to the Apache Software Foundation (ASF) under one - # or more contributor license agreements. See the NOTICE file - # distributed with this work for additional information - # regarding copyright ownership. The ASF licenses this file - # to you under the Apache License, Version 2.0 (the - # "License"); you may not use this file except in compliance - # with the License. You may obtain a copy of the License at - # - # http://www.apache.org/licenses/LICENSE-2.0 - # - # Unless required by applicable law or agreed to in writing, - # software distributed under the License is distributed on an - # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - # KIND, either express or implied. See the License for the - # specific language governing permissions and limitations - # under the License. -apiVersion: v1 -kind: Service -metadata: - name: s3ninja-svc -spec: - type: NodePort - ports: - - port: 9444 - targetPort: 9000 - protocol: TCP - name: http - nodePort: 30444 - selector: - app.kubernetes.io/name: s3ninja diff --git a/deploy/whisk-system/.gitkeep b/deploy/whisk-system/.gitkeep index e69de29b..d0a78df2 100644 --- a/deploy/whisk-system/.gitkeep +++ b/deploy/whisk-system/.gitkeep @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# diff --git a/deploy/zookeeper/zookeeper-003-sts.yaml b/deploy/zookeeper/zookeeper-003-sts.yaml index f2766c8d..a27d21e9 100644 --- a/deploy/zookeeper/zookeeper-003-sts.yaml +++ b/deploy/zookeeper/zookeeper-003-sts.yaml @@ -32,6 +32,8 @@ spec: name: zookeeper template: metadata: + annotations: + openserverless.org/messaging-isolation: "v1" labels: name: zookeeper app: zookeeper diff --git a/deploy/zookeeper/zookeeper-005-networkpolicy.yaml b/deploy/zookeeper/zookeeper-005-networkpolicy.yaml new file mode 100644 index 00000000..d73fa41a --- /dev/null +++ b/deploy/zookeeper/zookeeper-005-networkpolicy.yaml @@ -0,0 +1,50 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: openserverless-zookeeper-isolation + namespace: openserverless +spec: + podSelector: + matchLabels: + name: zookeeper + policyTypes: [Ingress] + ingress: + - from: + - podSelector: + matchExpressions: + - key: name + operator: In + values: [kafka, invoker, zookeeper] + - key: user-action-pod + operator: DoesNotExist + ports: + - port: 2181 + protocol: TCP + - from: + - podSelector: + matchLabels: + name: zookeeper + matchExpressions: + - key: user-action-pod + operator: DoesNotExist + ports: + - port: 2888 + protocol: TCP + - port: 3888 + protocol: TCP diff --git a/openserverless/actionexecutor.py b/openserverless/actionexecutor.py index 1a69a943..e4fdccab 100644 --- a/openserverless/actionexecutor.py +++ b/openserverless/actionexecutor.py @@ -22,6 +22,9 @@ import requests as req from datetime import datetime +# Bound tenant-controlled input before passing it to the cron parser. +MAX_CRON_EXPRESSION_LENGTH = 4096 + def check(f, what, res): if f: logging.info(f"OK: {what}") @@ -244,8 +247,11 @@ def get_autoexec(actionAnnotations): # no_execution if the action should not be triggered or the cron expression is not a valid one # def should_trigger(actionNamespace, actionName, actionCronExpression, currentDate, executionInterval): - if not cn.croniter.is_valid(actionCronExpression): - logging.warn(f"action {actionNamespace}/{actionName} cron expression {actionCronExpression} is not valid. Skipping execution") + if (not isinstance(actionCronExpression, str) + or not 0 < len(actionCronExpression) <= MAX_CRON_EXPRESSION_LENGTH + or not cn.croniter.is_valid(actionCronExpression)): + logging.warning("action %s/%s has an invalid cron expression. Skipping execution", + actionNamespace, actionName) return "no_execution" if not action_should_trigger(currentDate, executionInterval, actionCronExpression): @@ -389,8 +395,11 @@ def start(): if(len(actions) > 0): subjects = get_subjects(db, config['couchdb.controller.user'],config['couchdb.controller.password']) for action in actions: - handle_action(baseurl, currentDate, interval, action, subjects) + try: + handle_action(baseurl, currentDate, interval, action, subjects) + except Exception: + logging.exception("Failed to process scheduled action; continuing with remaining actions") else: logging.info('No cron aware action extracted. Exiting....') else: - logging.warn("CouchDB it is not available. Exiting....") \ No newline at end of file + logging.warn("CouchDB it is not available. Exiting....") diff --git a/openserverless/bucket.py b/openserverless/bucket.py deleted file mode 100644 index 2e989303..00000000 --- a/openserverless/bucket.py +++ /dev/null @@ -1,31 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -import openserverless.kustomize as nku -import openserverless.kube as kube -import os, os.path -import logging - -def create(): - spec = nku.kustom_list("s3ninja") - #kopf.adopt(spec) - return kube.apply(spec) - -def delete(): - spec = nku.kustom_list("s3ninja") - #kopf.adopt(spec) - return kube.delete(spec) diff --git a/openserverless/config.py b/openserverless/config.py index 8815171b..e89eab6a 100644 --- a/openserverless/config.py +++ b/openserverless/config.py @@ -259,4 +259,4 @@ def detect(): def dump_config(): import openserverless.config as cfg for k in cfg.getall(): - logging.debug(f"{k} = {cfg.get(k)}") + logging.debug("configuration key present: %s", k) diff --git a/openserverless/couchdb.py b/openserverless/couchdb.py index 29e9b24d..ad01f7de 100644 --- a/openserverless/couchdb.py +++ b/openserverless/couchdb.py @@ -215,7 +215,12 @@ def init_users_metadata(db): """ dbn = "users_metadata" res = check(db.wait_db_ready(60), "wait_db_ready", True) - res = check(db.create_db(dbn), "create_db: user_metadata", res) + if not db.check_db(dbn): + res = check(db.create_db(dbn), "create_db: user_metadata", res) + # Tenant actions have network access to CouchDB. Only the administrator + # used by the operator and admin API may read cross-tenant metadata. + admin = cfg.get('couchdb.admin.user', "COUCHDB_ADMIN_USER", "whisk_admin") + res = check(db.add_role(dbn, [admin]), "add_role: user_metadata", res) return res def init_compactions_config(db): diff --git a/openserverless/datastore_connection.py b/openserverless/datastore_connection.py new file mode 100644 index 00000000..5fd21c8a --- /dev/null +++ b/openserverless/datastore_connection.py @@ -0,0 +1,69 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +"""Reach a private service in-cluster or through an authenticated local tunnel.""" +import contextlib +import os +import re +import selectors +import subprocess +import time + + +@contextlib.contextmanager +def service_endpoint(service, port, namespace='openserverless'): + if not re.fullmatch(r'[a-z0-9][a-z0-9.-]*', service) or not re.fullmatch(r'[a-z0-9][a-z0-9-]*', namespace): + raise ValueError('Invalid datastore service') + if not isinstance(port, int) or not 0 < port < 65536: + raise ValueError('Invalid datastore port') + if os.environ.get('KUBERNETES_SERVICE_HOST'): + yield service, port + return + + # Use the current kubeconfig and namespace, as with the operator's other + # kubectl calls. Never expose a public listener or put credentials in argv. + process = subprocess.Popen( + ['kubectl', '-n', namespace, 'port-forward', '--address=127.0.0.1', + 'service/' + service, ':' + str(port)], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + try: + with selectors.DefaultSelector() as selector: + selector.register(process.stdout, selectors.EVENT_READ) + deadline = time.monotonic() + 30 + while time.monotonic() < deadline: + if selector.select(timeout=min(1, max(0, deadline-time.monotonic()))): + line = process.stdout.readline() + match = re.fullmatch(r'Forwarding from 127\.0\.0\.1:(\d+) -> ' + str(port), line.strip()) + if match: + local_port = int(match.group(1)) + if not 0 < local_port < 65536: + break + yield '127.0.0.1', local_port + return + if process.poll() is not None: + break + raise RuntimeError('Could not open the private datastore tunnel') + finally: + if process.poll() is None: + process.terminate() + try: + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + process.wait(timeout=5) + process.stdout.close() diff --git a/openserverless/datastore_security.py b/openserverless/datastore_security.py new file mode 100644 index 00000000..12925a0c --- /dev/null +++ b/openserverless/datastore_security.py @@ -0,0 +1,108 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Private application of datastore credentials and narrow ingress policies.""" +import base64 +import json +import subprocess + +import openserverless.kube as kube + + +def apply_private(spec, namespace='openserverless'): + # The generic wrapper includes stdin and stderr in error logs. + result = subprocess.run(['kubectl', '-n', namespace, 'apply', '-f', '-'], + input=json.dumps(spec), text=True, capture_output=True, + timeout=60) + if result.returncode: + raise RuntimeError('Could not apply private datastore resources') + + +def apply(spec): + items = spec.get('items', [spec]) + private = [item for item in items if item['kind'] == 'Secret'] + public = [item for item in items if item['kind'] != 'Secret'] + if private: + apply_private({'apiVersion': 'v1', 'kind': 'List', 'items': private}) + return kube.apply({'apiVersion': 'v1', 'kind': 'List', 'items': public}) + + +def ensure_jwt_key(owner=None): + """Keep a per-installation key; never reuse the legacy repository key.""" + name = 'openserverless-etcd-jwt-token-v2' + raw = kube.kubectl('get', 'secret', name, '--ignore-not-found', '-o', 'json', + debugresult=False) + if raw.strip(): + key = base64.b64decode(json.loads(raw)['data']['jwt-token.pem'], validate=True) + check = subprocess.run(['openssl', 'rsa', '-check', '-noout'], input=key, + capture_output=True, timeout=30) + if check.returncode: + raise RuntimeError('Invalid etcd signing key; refusing implicit rotation') + return name + # A missing key on an already patched cluster is a security state error. + current = kube.kubectl('get', 'sts', 'openserverless-etcd', '--ignore-not-found', + '-o', 'json', debugresult=False) + if current.strip(): + volumes = json.loads(current)['spec']['template']['spec'].get('volumes', []) + if any(v.get('secret', {}).get('secretName') == name for v in volumes): + raise RuntimeError('Missing etcd signing key; refusing implicit rotation') + result = subprocess.run(['openssl', 'genpkey', '-algorithm', 'RSA', + '-pkeyopt', 'rsa_keygen_bits:3072'], + capture_output=True, timeout=30) + if result.returncode: + raise RuntimeError('Could not generate etcd signing key') + resource = {'apiVersion': 'v1', 'kind': 'Secret', 'type': 'Opaque', + 'metadata': {'name': name, 'namespace': 'openserverless'}, + 'data': {'jwt-token.pem': base64.b64encode(result.stdout).decode()}} + if owner: + import kopf + kopf.append_owner_reference([resource], owner) + apply_private(resource) + return name + + +def policy(component): + if component == 'etcd': + selector = {'app.kubernetes.io/name': 'openserverless-etcd'} + ingress = [ + {'from': [{'podSelector': {'matchLabels': selector}}], + 'ports': [{'port': 2379, 'protocol': 'TCP'}, {'port': 2380, 'protocol': 'TCP'}]}, + {'from': [{'podSelector': {'matchLabels': {'name': 'openserverless-operator'}}}, + {'podSelector': {'matchLabels': {'name': 'openserverless-milvus'}}}], + 'ports': [{'port': 2379, 'protocol': 'TCP'}]}] + elif component == 'milvus': + selector = {'name': 'openserverless-milvus'} + ingress = [ + {'ports': [{'port': 19530, 'protocol': 'TCP'}]}, + {'from': [{'podSelector': {'matchLabels': { + 'app.kubernetes.io/name': 'prometheus', + 'app.kubernetes.io/instance': 'openserverless-prometheus'}}}, + {'podSelector': {'matchLabels': {'name': 'openserverless-operator'}}}], + 'ports': [{'port': 9091, 'protocol': 'TCP'}]}] + else: + raise ValueError('Unknown datastore component') + return {'apiVersion': 'networking.k8s.io/v1', 'kind': 'NetworkPolicy', + 'metadata': {'name': 'openserverless-'+component+'-isolation', 'namespace': 'openserverless'}, + 'spec': {'podSelector': {'matchLabels': selector}, + 'policyTypes': ['Ingress'], 'ingress': ingress}} + + +def finish_milvus_reconcile(): + kube.kubectl('rollout', 'status', 'deployment/openserverless-milvus-standalone', '--timeout=300s') + # Remove the old ConfigMap AND its last-applied annotation by deleting it. + # Its contents include S3 and Milvus admin credentials as well as etcd root. + kube.kubectl('delete', 'configmap', 'openserverless-milvus', '--ignore-not-found') diff --git a/openserverless/enterprise_util.py b/openserverless/enterprise_util.py index 64c65ba4..c875265f 100644 --- a/openserverless/enterprise_util.py +++ b/openserverless/enterprise_util.py @@ -185,7 +185,7 @@ def get_am_config_data(): "slack_api_url": cfg.get("monitoring.alert-manager.slack.slack_api_url","SLACK_API_URL",""), "slack_default": cfg.get("monitoring.alert-manager.slack.default") or False, "gmail": cfg.get("monitoring.alert-manager.gmail.enabled") or False, - "gmail_default": cfg.get("monitoring.alert-manager.gmail.false") or False, + "gmail_default": cfg.get("monitoring.alert-manager.gmail.default") or False, "email_recipients": cfg.get("monitoring.alert-manager.gmail.to"), "email_from": cfg.get("monitoring.alert-manager.gmail.from"), "gmail_username": cfg.get("monitoring.alert-manager.gmail.username","GMAIL_USERNAME",""), diff --git a/openserverless/etcd.py b/openserverless/etcd.py index 6d14f1f4..b9a95368 100644 --- a/openserverless/etcd.py +++ b/openserverless/etcd.py @@ -21,6 +21,8 @@ import openserverless.config as cfg import openserverless.template as ntp import openserverless.util as util +from openserverless import datastore_security +from openserverless.etcd_admin import create_etcd_user, delete_db_user import os.path import logging import kopf @@ -44,15 +46,20 @@ def create(owner=None): kust = kus.patchTemplates("etcd",tplp , data) kust += kus.patchGenericEntry("Secret","openserverless-etcd-secret","/data/rootPassword",util.b64_encode(data['root_password'])) - spec = kus.kustom_list("etcd", kust, templates=spec_templates, data=data) + datastore_security.ensure_jwt_key(owner) + spec = kus.restricted_kustom_list("etcd", kust, templates=spec_templates, data=data, + templates_filter=["etcd-secret.yaml", "etcd-sts.yaml", "etcd-svc.yaml"]) + spec["items"].append(datastore_security.policy("etcd")) if owner: kopf.append_owner_reference(spec['items'], owner) else: cfg.put("state.etcd.spec", spec) - res = kube.apply(spec) + res = datastore_security.apply(spec) + kube.kubectl("rollout", "status", "statefulset/openserverless-etcd", "--timeout=300s") wait_for_etcd_ready() + kube.kubectl("delete", "secret", "openserverless-etcd-jwt-token", "--ignore-not-found") logging.info(f"create etcd: {res}") return res @@ -61,78 +68,6 @@ def wait_for_etcd_ready(): # dynamically detect etcd pod and wait for readiness util.wait_for_pod_ready(r"{.items[?(@.metadata.labels.app\.kubernetes\.io\/name == 'openserverless-etcd')].metadata.name}") -def render_etcd_script(namespace,template,data): - """ - uses the given template to render a sh script to execute via bash shell. - """ - out = f"/tmp/__{namespace}_{template}" - file = ntp.spool_template(template, out, data) - return os.path.abspath(file) - -def exec_etcd_script(pod_name,path_to_etcd_script): - logging.info(f"passing script {path_to_etcd_script} to pod {pod_name}") - res = kube.kubectl("cp",path_to_etcd_script,f"{pod_name}:{path_to_etcd_script}") - res = kube.kubectl("exec","-i",pod_name,"--","/bin/bash","-c",f"chmod +x {path_to_etcd_script}") - res = kube.kubectl("exec","-i",pod_name,"--","/bin/bash","-c",path_to_etcd_script) - os.remove(path_to_etcd_script) - return res - - -def create_etcd_user(username:str, password:str, prefix:str): - """ - Creates a new ETCD username with the given password and assign redwrite permission on the given prefix - return: True if operation succeed, False otherwise. - """ - logging.info(f"authorizing new ETCD user {username} on prefix {prefix}") - - try: - data = {} - data["username"]=username - data["password"]=password - data["prefix"]=prefix - data["mode"]="create" - - path_to_etcd_script = render_etcd_script(username,"etcd_manage_user_tpl.sh",data) - pod_name = util.get_pod_name_by_selector("name=openserverless-etcd","{.items[0].metadata.name}") - - if(pod_name): - res = exec_etcd_script(pod_name,path_to_etcd_script) - if res: - return True - else: - logging.error(f"failed to add ETCD username {username}") - return False - - return False - except Exception as e: - logging.error(f"failed to add ETCD username {username}: {e}") - return False - -def delete_db_user(username): - """ - Reomves the specified user from the ETCD instance. - """ - logging.info(f"removing ETCD user {username}") - try: - data = {} - data["username"]=username - data["mode"]="delete" - - path_to_etcd_script = render_etcd_script(username,"etcd_manager_user_tpl.sh",data) - pod_name = util.get_pod_name_by_selector("name=openserverless-etcd","{.items[0].metadata.name}") - - if(pod_name): - res = exec_etcd_script(pod_name,path_to_etcd_script) - if res: - return res - else: - logging.error(f"failed to remove ETCD username {username}") - - return None - except Exception as e: - logging.error(f"failed to remove ETCD username {username} authorization id and key: {e}") - return None - def delete_by_owner(): spec = kus.build("etcd") res = kube.delete(spec) diff --git a/openserverless/etcd_admin.py b/openserverless/etcd_admin.py new file mode 100644 index 00000000..fe8ed522 --- /dev/null +++ b/openserverless/etcd_admin.py @@ -0,0 +1,114 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Authenticated etcd 3.5 administration without shell interpolation.""" +import base64 +import contextlib +import logging +import re +import requests +import openserverless.util as util +from openserverless.datastore_connection import service_endpoint + +def _post(session, endpoint, path, body, allowed=()): + for attempt in range(2): + try: + r=session.post(endpoint+'/v3/'+path,json=body,timeout=8) + result=r.json() + except (requests.RequestException,ValueError): + raise RuntimeError('etcd administration request failed') from None + error=result.get('error') or result.get('message','') + credentials=getattr(session,'_ops_etcd_credentials',None) + if attempt==0 and credentials and error in ( + 'etcdserver: revision of auth store is old', 'etcdserver: invalid auth token'): + # Auth-store updates can invalidate a JWT between admin operations. + # Refresh only this authenticated session, never substitute root for a tenant. + try: + auth=session.post(endpoint+'/v3/auth/authenticate',json=credentials,timeout=8) + token=auth.json().get('token') if auth.ok else None + except (requests.RequestException,ValueError): token=None + if not token: raise RuntimeError('Could not renew etcd administration authentication') + session.headers['Authorization']=token + continue + if error in allowed: return result + if not r.ok or error: + raise RuntimeError('etcd administration request was rejected') + return result + + +@contextlib.contextmanager +def _admin(): + data = util.get_etcd_config_data() + with service_endpoint(data['name'], 2379) as (host, port), requests.Session() as session: + endpoint = 'http://' + host + ':' + str(port) + session.trust_env = False + session._ops_etcd_credentials = {'name': 'root', 'password': data['root_password']} + session.headers['Authorization'] = _post(session, endpoint, 'auth/authenticate', + session._ops_etcd_credentials)['token'] + yield session, endpoint + + +def create_etcd_user(username, password, prefix, readonly_keys=()): + if not re.fullmatch(r'[A-Za-z0-9_-]{1,64}',username) or username=='root' or not prefix.strip('/'): + raise ValueError('Invalid etcd user or key prefix') + try: + with _admin() as (session,endpoint): + result=_post(session,endpoint,'auth/user/add',{'name':username,'password':password}, + allowed=('etcdserver: user name already exists',)) + if result.get('error') or result.get('message'): + _post(session,endpoint,'auth/authenticate',{'name':username,'password':password}) + role=username+'_role' + _post(session,endpoint,'auth/role/add',{'name':role}, + allowed=('etcdserver: role name already exists',)) + key=(prefix.rstrip('/')+'/').encode() + end=bytearray(key);end[-1]+=1 + _post(session,endpoint,'auth/role/grant',{'name':role,'perm':{'permType':'READWRITE', + 'key':base64.b64encode(key).decode(),'range_end':base64.b64encode(end).decode()}}) + for exact_key in readonly_keys: + _post(session, endpoint, 'auth/role/grant', {'name': role, 'perm': { + 'permType': 'READ', 'key': base64.b64encode(exact_key.encode()).decode()}}) + _post(session,endpoint,'auth/user/grant',{'user':username,'role':role}) + identity = _post(session, endpoint, 'auth/user/get', {'name': username}) + permissions = _post(session, endpoint, 'auth/role/get', {'role': role}).get('perm', []) + expected = {'permType': 'READWRITE', 'key': base64.b64encode(key).decode(), + 'range_end': base64.b64encode(end).decode()} + expected_permissions = [expected] + [ + {'permType': 'READ', 'key': base64.b64encode(k.encode()).decode()} + for k in readonly_keys] + def normalized(values): + return sorted((v.get('permType', 'READ'), v['key'], v.get('range_end', '')) for v in values) + if identity.get('roles') != [role] or normalized(permissions) != normalized(expected_permissions): + raise RuntimeError('Existing etcd account exceeds the required prefix scope') + return True + except RuntimeError: + logging.error('Could not provision the etcd user') + return False + + +def delete_db_user(username): + if not re.fullmatch(r'[A-Za-z0-9_-]{1,64}',username) or username=='root': + raise ValueError('Invalid etcd user') + try: + with _admin() as (session,endpoint): + _post(session,endpoint,'auth/user/delete',{'name':username}, + allowed=('etcdserver: user name not found',)) + _post(session,endpoint,'auth/role/delete',{'role':username+'_role'}, + allowed=('etcdserver: role name not found',)) + return True + except RuntimeError: + logging.error('Could not remove the etcd user') + return False diff --git a/openserverless/ferretdb.py b/openserverless/ferretdb.py index 6d911e62..df4f87b4 100644 --- a/openserverless/ferretdb.py +++ b/openserverless/ferretdb.py @@ -160,10 +160,11 @@ def _add_mdb_user_metadata(user_metadata, data): mdb_url = f"mongodb://{auth}@{mdb_pod_name}.{mdb_service_name}.{mdb_ns}.svc.cluster.local:27017/{database}?connectTimeoutMS=60000&authMechanism=PLAIN" user_metadata.add_metadata("MONGODB_URL",mdb_url) - return None + return True + return False except Exception as e: logging.error(f"failed to build mongodb_url for {data['database']}: {e}") - return None + return False def create_db_user(ucfg: UserConfig, user_metadata: UserMetadata): database = ucfg.get('mongodb.database') @@ -178,16 +179,23 @@ def create_db_user(ucfg: UserConfig, user_metadata: UserMetadata): data["password"]=ucfg.get('mongodb.password') data["mode"]="create" - path_to_pgpass = postgres.render_postgres_script(f"{namespace}_ferretdb","pgpass_tpl.properties",data) - path_to_mdb_script = postgres.render_postgres_script(f"{namespace}_ferretdb","postgres_manage_user_tpl.sql",data) - pod_name = util.get_pod_name("{.items[?(@.metadata.labels.app == 'openserverless-postgres')].metadata.name}") + if not util.validate_namespace(namespace): + raise ValueError("Invalid FerretDB namespace") + if not util.validate_database_name(data["database"]) or not util.validate_database_name(data["username"]): + raise ValueError("Invalid FerretDB database or username") + # The renderer needs the Kubernetes namespace as a safe file prefix; + # the PostgreSQL names retain their existing _ferretdb suffix. + path_to_pgpass = postgres.render_postgres_script(namespace,"pgpass_tpl.properties",data) + path_to_mdb_script = postgres.render_postgres_script(namespace,"postgres_manage_user_tpl.sql",data) + pod_name = util.get_pod_name_by_selector("app=openserverless-postgres","{.items[?(@.metadata.labels.replicationRole == 'primary')].metadata.name}") if(pod_name): res = postgres.exec_psql_command(pod_name,path_to_mdb_script,path_to_pgpass) if res: - _add_mdb_user_metadata(user_metadata, data) - return res + if _add_mdb_user_metadata(user_metadata, data): + return res + logging.error("FerretDB user created but connection metadata is unavailable") else: logging.error("failed to execute SQL script on Postgres DB to enable MongoDB (FerretDB) user") @@ -205,9 +213,13 @@ def delete_db_user(namespace, database): data["username"]=f"{namespace}_ferretdb" data["mode"]="delete" - path_to_pgpass = postgres.render_postgres_script(f"{namespace}_ferretdb","pgpass_tpl.properties",data) - path_to_mdb_script = postgres.render_postgres_script(f"{namespace}_ferretdb","postgres_manage_user_tpl.sql",data) - pod_name = util.get_pod_name("{.items[?(@.metadata.labels.app == 'openserverless-postgres')].metadata.name}") + if not util.validate_namespace(namespace): + raise ValueError("Invalid FerretDB namespace") + if not util.validate_database_name(data["database"]) or not util.validate_database_name(data["username"]): + raise ValueError("Invalid FerretDB database or username") + path_to_pgpass = postgres.render_postgres_script(namespace,"pgpass_tpl.properties",data) + path_to_mdb_script = postgres.render_postgres_script(namespace,"postgres_manage_user_tpl.sql",data) + pod_name = util.get_pod_name_by_selector("app=openserverless-postgres","{.items[?(@.metadata.labels.replicationRole == 'primary')].metadata.name}") if(pod_name): res = postgres.exec_psql_command(pod_name,path_to_mdb_script,path_to_pgpass) @@ -216,4 +228,4 @@ def delete_db_user(namespace, database): return None except Exception as e: logging.error(f"failed to remove Ferretdb database {namespace} authorization id and key: {e}") - return None \ No newline at end of file + return None diff --git a/openserverless/issuer.py b/openserverless/issuer.py index 7483cd2b..cabb61e6 100644 --- a/openserverless/issuer.py +++ b/openserverless/issuer.py @@ -59,7 +59,7 @@ def create(owner=None): def delete_by_owner(): spec = "deploy/issuer/__cluster-issuer.yaml" res = kube.kubectl("delete", "-f", spec,namespace=None) - logging.info(f"delete minio: {res}") + logging.info(f"delete cluster issuer: {res}") return res def delete_by_spec(): diff --git a/openserverless/kafka.py b/openserverless/kafka.py index 2aad70b4..36864b06 100644 --- a/openserverless/kafka.py +++ b/openserverless/kafka.py @@ -22,6 +22,7 @@ import openserverless.enterprise_util as cfg_util import openserverless.enterprise_util as cfg_util import openserverless.util as util +import openserverless.messaging_security as security def get_kafka_pod_name(): pod_name = kube.kubectl("get", "pods", jsonpath="{.items[?(@.metadata.labels.app == 'kafka')].metadata.name}") @@ -62,7 +63,7 @@ def create(owner=None): cfg.put("state.kafka.spec", spec) - res = kube.apply(spec) + res = security.apply(spec, 'kafka') # dynamically detect kafka pod and wait for readiness util.wait_for_pod_ready("{.items[?(@.metadata.labels.app == 'kafka')].metadata.name}") @@ -76,6 +77,6 @@ def delete(): spec = cfg.get("state.kafka.spec") res = False if spec: - res = kube.delete(spec) + res = security.delete(spec, 'kafka') logging.info(f"delete kafka: {res}") - return res \ No newline at end of file + return res diff --git a/openserverless/kopf_util.py b/openserverless/kopf_util.py index bacf6b96..25a0f224 100644 --- a/openserverless/kopf_util.py +++ b/openserverless/kopf_util.py @@ -65,14 +65,6 @@ def check_component(response: dict, item: dict, cmp_spec, cmp_key): else: response[cmp_key]="delete" -def check_minio_ingresses(response: dict, item: dict): - """ - Forces an update of minio-ingresses if needed - """ - if(item['path']=='spec.minio.ingress.s3-enabled' or item['path']=='spec.minio.ingress.console-enabled'): - if(item['new']): - response["minio-ingresses"]="update" - def check_seaweedfs_ingresses(response: dict, item: dict): """ Forces an update of seaweed-ingresses if needed @@ -101,7 +93,6 @@ def evaluate_differences(response: dict, differences: list): check_component(response, d,"spec.components.zookeeper","zookeeper") check_component(response, d,"spec.components.redis","redis") check_component(response, d,"spec.components.cron","cron") - check_component(response, d,"spec.components.minio","minio") check_component(response, d,"spec.components.static","static") check_component(response, d,"spec.components.postgres","postgres") check_component(response, d,"spec.components.quota","quota") @@ -111,7 +102,6 @@ def evaluate_differences(response: dict, differences: list): check_component(response, d,"spec.components.seaweedfs","seaweedfs") openwhisk(response, d) endpoint(response, d) - check_minio_ingresses(response, d) check_seaweedfs_ingresses(response, d) def detect_component_changes(kopf_diff): diff --git a/openserverless/main.py b/openserverless/main.py index 9e9c57ee..ca02d9dd 100644 --- a/openserverless/main.py +++ b/openserverless/main.py @@ -22,15 +22,14 @@ import openserverless.kube as kube import openserverless.redis as redis import openserverless.couchdb as couchdb -import openserverless.bucket as bucket import openserverless.openwhisk as openwhisk import openserverless.cronjob as cron -import openserverless.ferretdb as mongodb +import openserverless.ferretdb as ferretdb import openserverless.issuer as issuer import openserverless.endpoint as endpoint -import openserverless.minio_deploy as minio import openserverless.zookeeper as zookeeper import openserverless.kafka as kafka +import openserverless.messaging_security as messaging_security import openserverless.invoker as invoker import openserverless.patcher as patcher import openserverless.storage_static as static @@ -91,18 +90,13 @@ def whisk_create(spec, name, **kwargs): "endpoint": "?", # Http/s controller endpoint # Http/s controller endpoint "issuer": "?", # ClusterIssuer configuration "ingress": "?", # Ingress configuration - "minio": "?", # Minio configuration - "static": "?", # Minio static endpoint provider + "static": "?", # S3 static endpoint provider "zookeeper": "?", #Zookeeper configuration "quota":"?", #Quota configuration "etcd":"?", #Etcd configuration "milvus":"?" #Milvus configuration } - if cfg.get('components.minio') and cfg.get('components.seaweedfs'): - state['controller']= "NotValid" - raise kopf.PermanentError("Storage support for MINIO and SEAWEEDFS could not be activated simultaneously.") - runtime = cfg.get('openserverless.kube') logging.info(f"kubernetes engine in use={runtime}") @@ -177,13 +171,6 @@ def whisk_create(spec, name, **kwargs): else: state['cron'] = "off" - if cfg.get('components.minio'): - msg = minio.create(owner) - logging.info(msg) - state['minio'] = "on" - else: - state['minio'] = "off" - if cfg.get('components.seaweedfs'): msg = seaweedfs.create(owner) logging.info(msg) @@ -206,7 +193,7 @@ def whisk_create(spec, name, **kwargs): state['postgres'] = "off" if cfg.get('components.mongodb'): - msg = mongodb.create(owner) + msg = ferretdb.create(owner) logging.info(msg) state['mongodb'] = "on" else: @@ -343,7 +330,7 @@ def whisk_delete(spec, **kwargs): logging.info(msg) if cfg.get("components.mongodb"): - msg = mongodb.delete() + msg = ferretdb.delete() logging.info(msg) if cfg.get("components.cron"): @@ -354,10 +341,6 @@ def whisk_delete(spec, **kwargs): msg = static.delete() logging.info(msg) - if cfg.get("components.minio"): - msg = minio.delete() - logging.info(msg) - if cfg.get("components.seaweedfs"): msg = seaweedfs.delete() logging.info(msg) @@ -417,11 +400,14 @@ def whisk_update(spec, status, namespace, diff, name, **kwargs): operator_util.config_from_spec(spec,handler_type="on_update") owner = kube.get(f"wsk/{name}") + messaging_security.reconcile(owner) patcher.patch(diff, status, owner, name) @kopf.on.resume('openserverless.org', 'v1', 'whisks') def whisk_resume(spec, status, name, **kwargs): operator_util.config_from_spec(spec, handler_type="on_resume") + if cfg.get('components.zookeeper') or cfg.get('components.kafka'): + messaging_security.reconcile(kube.get(f"wsk/{name}")) operator_util.whisk_post_resume(name) def runtimes_filter(name, type, **kwargs): diff --git a/openserverless/messaging_security.py b/openserverless/messaging_security.py new file mode 100644 index 00000000..61fe17b4 --- /dev/null +++ b/openserverless/messaging_security.py @@ -0,0 +1,68 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Apply the messaging network boundary before creating or updating workloads.""" + +import openserverless.kube as kube +import openserverless.config as cfg + + +def reconcile(owner): + """Install the boundary on existing clusters during operator resume/update.""" + # Import lazily: component create functions use this module's apply helper. + from openserverless import kafka, zookeeper + + enabled = [(name, module) for name, module in + [('zookeeper', zookeeper), ('kafka', kafka)] + if cfg.get('components.' + name)] + if not enabled: + return + # Never turn a failed owner lookup into an ownerless component deployment. + if not owner or not owner.get('metadata', {}).get('uid'): + raise RuntimeError('Cannot reconcile messaging isolation without the Whisk owner') + for name, module in enabled: + module.create(owner) + + +def apply(spec, component): + policy_name = 'openserverless-' + component + '-isolation' + policies = [item for item in spec['items'] + if item['kind'] == 'NetworkPolicy'] + if not any(item['metadata']['name'] == policy_name for item in policies): + raise RuntimeError('Missing ' + component + ' isolation policy') + # Do not create/reconcile an exposed service if policy application fails. + # The complete spec still owns the policy for reconciliation and deletion. + kube.apply({'apiVersion': 'v1', 'kind': 'List', 'items': policies}) + result = kube.apply(spec) + # The template boundary revision rolls legacy pods to close existing + # anonymous sessions as well as blocking new connections. + kube.kubectl('rollout', 'status', 'statefulset/' + component, '--timeout=300s') + return result + + +def delete(spec, component): + policies = [item for item in spec['items'] if item['kind'] == 'NetworkPolicy'] + workloads = [item for item in spec['items'] if item['kind'] != 'NetworkPolicy'] + result = kube.delete({'apiVersion': 'v1', 'kind': 'List', 'items': workloads}) + # StatefulSet deletion can return while its dependent pods are terminating. + # Keep the boundary until those pods are actually gone; fail closed on error. + pods = kube.kubectl('get', 'pods', '-l', 'name=' + component, '-o', 'name').split() + if pods: + kube.kubectl('wait', '--for=delete', *pods, '--timeout=300s') + if policies: + result += kube.delete({'apiVersion': 'v1', 'kind': 'List', 'items': policies}) + return result diff --git a/openserverless/milvus_operator.py b/openserverless/milvus_operator.py index e7275eeb..a9f9342a 100644 --- a/openserverless/milvus_operator.py +++ b/openserverless/milvus_operator.py @@ -22,11 +22,9 @@ import openserverless.config as cfg import openserverless.util as util import openserverless.operator_util as operator_util -import openserverless.minio_util as mutil import openserverless.etcd as etcd from openserverless.opaque_secret import OpaqueSecret -from openserverless.minio_util import MinioClient def create(owner=None): """ diff --git a/openserverless/milvus_standalone.py b/openserverless/milvus_standalone.py index f5632fb9..b48ba87d 100644 --- a/openserverless/milvus_standalone.py +++ b/openserverless/milvus_standalone.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. # +from openserverless import etcd, datastore_security import time import kopf, logging @@ -23,7 +24,6 @@ import openserverless.config as cfg import openserverless.util as util import openserverless.operator_util as operator_util -import openserverless.minio_util as mutil import openserverless.openwhisk as openwhisk from openserverless.milvus_admin_client import MilvusAdminClient @@ -96,12 +96,14 @@ def create(owner=None): kust = patchEntries(data) mspec = kus.kustom_list(dir, kust, templates=[], data=data) + mspec["items"].append(datastore_security.policy("milvus")) if owner: kopf.append_owner_reference(mspec['items'], owner) else: cfg.put("state.milvus.spec", mspec) - kube.apply(mspec) + datastore_security.apply(mspec) + datastore_security.finish_milvus_reconcile() util.wait_for_pod_ready( r"{.items[?(@.metadata.labels.app\.kubernetes\.io\/instance == 'openserverless-milvus')].metadata.name}") @@ -118,24 +120,6 @@ def create(owner=None): return res -def create_minio_milvus_account(data: dict): - """" - Creates technical accounts for MINIO - """ - try: - minioClient = mutil.MinioClient() - bucket_policy_names = [] - bucket_policy_names.append(f"{data['milvus_bucket_name']}/*") - - res = util.check(minioClient.add_user(data["milvus_s3_username"], data["milvus_s3_password"]), - "create_milvus_s3_user", True) - res = util.check(minioClient.make_bucket(data["milvus_bucket_name"]), "create_milvus_s3_bucket", res) - return util.check(minioClient.assign_rw_bucket_policy_to_user(data["milvus_s3_username"], bucket_policy_names), - "assign_milvus_s3_bucket_policy", res) - except Exception as ex: - logging.error("Could not create milvus MINIO accounts", ex) - return False - def create_seaweedfs_milvus_account(data: dict): """" Creates technical accounts for SEAWEEDFS @@ -150,30 +134,12 @@ def create_seaweedfs_milvus_account(data: dict): def create_milvus_accounts(data: dict): """" - Creates technical accounts for ETCD and MINIO - """ - # currently we use the ETCD root password, so we skip the ETCD user creation. - # res = util.check(etcd.create_etcd_user(data['milvus_etcd_username'],data['milvus_etcd_password'],data['milvus_etcd_prefix']),"create_etcd_milvus_user",True) - - if cfg.get('components.minio'): - return create_minio_milvus_account(data) - - if cfg.get('components.seaweedfs'): - return create_seaweedfs_milvus_account(data) - -def delete_minio_milvus_account(data: dict): + Creates technical accounts for ETCD and SEAWEEDFS """ - Deletes technical accounts for MINIO - """ - try: - logging.info("removing milvus minio technical accounts.") - minioClient = mutil.MinioClient() - res = util.check(minioClient.remove_user(data["milvus_s3_username"]), "remove_user", True) - return util.check(minioClient.force_bucket_remove(data["milvus_bucket_name"]), "force_bucket_remove", res) - - except Exception as ex: - logging.error("Could not delete milvus MINIO accounts", ex) - return False + if not etcd.create_etcd_user(data['milvus_etcd_username'], data['milvus_etcd_password'], + data['milvus_etcd_prefix'], readonly_keys=('health',)): + return False + return create_seaweedfs_milvus_account(data) def delete_seaweedfs_milvus_account(data: dict): """ @@ -191,16 +157,13 @@ def delete_seaweedfs_milvus_account(data: dict): def delete_milvus_accounts(data: dict): """" - Deletes technical accounts for ETCD and MINIO + Deletes technical accounts for ETCD and SEAWEEDFS """ - # currently we use the ETCD root password, so we skip the ETCD user deletion. + if not etcd.delete_db_user(data["milvus_etcd_username"]): + return False logging.info("removing milvus technical accounts.") - if cfg.get('components.minio'): - return delete_minio_milvus_account(data) - - if cfg.get('components.seaweedfs'): - return delete_seaweedfs_milvus_account(data) + return delete_seaweedfs_milvus_account(data) def create_default_milvus_database(data): @@ -209,6 +172,25 @@ def create_default_milvus_database(data): """ logging.info("*** configuring MILVUS database for openserverless") adminClient = MilvusAdminClient() + # Security reconciliation must preserve an already provisioned system user. + # Authenticate with its configured credential; never reset it on an error. + from openserverless.milvus_simple_client import MilvusSimpleClient + import requests + try: + client = MilvusSimpleClient(uri=adminClient.milvus_url, token=adminClient.milvus_admin_token) + users = client.list_users() + if "openserverless" in users: + response = requests.post(adminClient.milvus_url + '/v2/vectordb/collections/list', + headers={'Authorization': 'Bearer openserverless:' + data['openserverless_password']}, + json={'dbName': 'openserverless'}, timeout=10) + result = response.json() + if not response.ok or result.get('code') != 0 or 'data' not in result: + return False + _annotate_nuv_milvus_metadata(data) + return True + except (requests.RequestException, ValueError, TypeError): + logging.error('Could not verify the existing Milvus system account') + return False res = adminClient.setup_user("openserverless", data["openserverless_password"], "openserverless") if (res): @@ -222,7 +204,6 @@ def create_default_milvus_database(data): def _annotate_nuv_milvus_metadata(data): """ annotate openserverless configmap with entries for MILVUS connectivity MILVUS_HOST, MILVUS_PORT, MILVUS_TOKEN, MILVUS_DB_NAME - this is becasue MINIO """ try: milvus_service = util.get_service( diff --git a/openserverless/minio_deploy.py b/openserverless/minio_deploy.py deleted file mode 100644 index 9e3216e3..00000000 --- a/openserverless/minio_deploy.py +++ /dev/null @@ -1,303 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -import kopf, logging, time, os -import openserverless.kube as kube -import openserverless.kustomize as kus -import openserverless.config as cfg -import openserverless.util as util -import openserverless.minio_util as mutil -import openserverless.openwhisk as openwhisk -import openserverless.minio_ingress as minio_ingress -import openserverless.operator_util as operator_util - -from openserverless.user_config import UserConfig -from openserverless.user_metadata import UserMetadata -from openserverless.minio_util import MinioClient - -def _add_miniouser_metadata(ucfg: UserConfig, user_metadata:UserMetadata): - """ - adds entries for minio connectivity S3_HOST, S3_PORT, S3_ACCESS_KEY, S3_SECRET_KEY - this is becasue MINIO - """ - - try: - minio_service = util.get_service("{.items[?(@.spec.selector.app == 'minio')]}") - if(minio_service): - minio_host = f"{minio_service['metadata']['name']}.{minio_service['metadata']['namespace']}.svc.cluster.local" - access_key = ucfg.get('namespace') - secret_key = ucfg.get("object-storage.password") - user_metadata.add_metadata("S3_PROVIDER","minio") - user_metadata.add_metadata("S3_HOST",minio_host) - user_metadata.add_metadata("S3_ACCESS_KEY",access_key) - user_metadata.add_metadata("S3_SECRET_KEY",secret_key) - - user_metadata.add_safely_from_cm("S3_API_URL", '{.metadata.annotations.s3_api_url}') - user_metadata.add_safely_from_cm("S3_CONSOLE_URL", '{.metadata.annotations.s3_console_url}') - - ports = list(minio_service['spec']['ports']) - for port in ports: - if(port['name']=='minio-api'): - user_metadata.add_metadata("S3_PORT",port['port']) - - - return None - except Exception as e: - logging.error(f"failed to build MINIO metadata for {ucfg.get('namespace')}: {e}") - return None - -def find_content_path(filename): - absolute_path = os.path.dirname(__file__) - relative_path = "../deploy/content" - return os.path.join(absolute_path, relative_path, filename) - -def create(owner=None): - logging.info(f"*** configuring minio standalone") - - data = util.get_minio_config_data() - - tplp = ["00-minio-pvc.yaml","01-minio-dep.yaml","02-minio-svc.yaml"] - - if(data['affinity'] or data['tolerations']): - tplp.append("affinity-tolerance-dep-core-attach.yaml") - - kust = kus.patchTemplates("minio", tplp, data) - spec = kus.kustom_list("minio", kust, templates=[], data=data) - - if owner: - kopf.append_owner_reference(spec['items'], owner) - else: - cfg.put("state.minio.spec", spec) - - res = kube.apply(spec) - - # dynamically detect minio pod and wait for readiness - util.wait_for_pod_ready("{.items[?(@.metadata.labels.app == 'minio')].metadata.name}") - create_nuv_storage(data) - minio_ingress.create_minio_ingresses(data, owner) - logging.info("*** configured minio standalone") - return res - -def _annotate_nuv_metadata(data): - """ - annotate openserverless configmap with entries for minio connectivity S3_ENDPOINT, S3_PORT, S3_ACCESS_KEY, S3_SECRET_KEY - this is becasue MINIO - """ - try: - minio_service = util.get_service("{.items[?(@.spec.selector.app == 'minio')]}") - if(minio_service): - minio_host = f"{minio_service['metadata']['name']}.{minio_service['metadata']['namespace']}.svc.cluster.local" - access_key = data["minio_nuv_user"] - secret_key = data["minio_nuv_password"] - openwhisk.annotate(f"s3_host={minio_host}") - openwhisk.annotate(f"s3_access_key={access_key}") - openwhisk.annotate(f"s3_secret_key={secret_key}") - openwhisk.annotate("s3_provider=minio") - - ports = list(minio_service['spec']['ports']) - for port in ports: - if(port['name']=='minio-api'): - openwhisk.annotate(f"s3_port={port['port']}") - return None - except Exception as e: - logging.error(f"failed to build minio_host for openserverless: {e}") - return None - -def create_nuv_storage(data): - """ - Creates openserverless MINIO custom resources - """ - logging.info(f"*** configuring MINIO storage for openserverless") - # introduce a 10 seconds delay to be sure that MINIO server is up and running completely as pod readines not to be enough - time.sleep(10) - minioClient = mutil.MinioClient() - res = minioClient.add_user(data["minio_nuv_user"], data["minio_nuv_password"]) - - if(res): - _annotate_nuv_metadata(data) - bucket_policy_names = [] - - logging.info("*** adding openserverless MINIO data bucket") - res = minioClient.make_bucket("openserverless-data") - bucket_policy_names.append("openserverless-data/*") - - if(res): - openwhisk.annotate("s3_bucket_data=openserverless-data") - - logging.info("*** adding openserverless MINIO static public bucket") - res = minioClient.make_public_bucket("openserverless-web") - bucket_policy_names.append("openserverless-web/*") - - if(res): - openwhisk.annotate("s3_bucket_static=openserverless-web") - content_path = find_content_path("index.html") - - if(content_path): - logging.info(f"uploading example content to openserverless-web from {content_path}") - res = minioClient.upload_folder_content(content_path,"openserverless-web") - else: - logging.warn("could not find example static content to upload") - - if(len(bucket_policy_names)>0): - logging.info(f"granting rw access to created policies under username {data['minio_nuv_user']}") - minioClient.assign_rw_bucket_policy_to_user(data["minio_nuv_user"],bucket_policy_names) - - logging.info("*** configured MINIO storage for openserverless") - -def assign_bucket_quota(bucket_name, quota, minioClient:MinioClient): - if not quota.lower() in ['auto'] and quota.isnumeric(): - logging.info(f"*** setting quota on bucket {bucket_name} with hardlimit to {quota}m") - res = minioClient.assign_quota_to_bucket(bucket_name,quota) - - if res: - logging.info(f"*** quota on bucket {bucket_name} set successfully") - else: - logging.warn(f"*** skipping quota set on bucket {bucket_name}. Requested quota values is {quota}") - - -def create_ow_storage(state, ucfg: UserConfig, user_metadata: UserMetadata, owner=None): - minioClient = mutil.MinioClient() - namespace = ucfg.get("namespace") - secretkey = ucfg.get("object-storage.password") - - logging.info(f"*** configuring storage for namespace {namespace}") - - res = minioClient.add_user(namespace, secretkey) - state['storage_user']=res - - if(res): - _add_miniouser_metadata(ucfg, user_metadata) - - bucket_policy_names = [] - - if(ucfg.get('object-storage.data.enabled')): - bucket_name = ucfg.get('object-storage.data.bucket') - logging.info(f"*** adding private bucket {bucket_name} for {namespace}") - res = minioClient.make_bucket(bucket_name) - bucket_policy_names.append(f"{bucket_name}/*") - state['storage_data']=res - - if(res): - user_metadata.add_metadata("S3_BUCKET_DATA",bucket_name) - ucfg.put("S3_BUCKET_DATA",bucket_name) - - if ucfg.exists('object-storage.quota'): - assign_bucket_quota(bucket_name,ucfg.get('object-storage.quota'), minioClient) - - if(ucfg.get('object-storage.route.enabled')): - bucket_name = ucfg.get("object-storage.route.bucket") - logging.info(f"*** adding public bucket {bucket_name} for {namespace}") - res = minioClient.make_public_bucket(bucket_name) - bucket_policy_names.append(f"{bucket_name}/*") - - if(res): - user_metadata.add_metadata("S3_BUCKET_STATIC",bucket_name) - ucfg.put("S3_BUCKET_STATIC",bucket_name) - - if ucfg.exists('object-storage.quota'): - assign_bucket_quota(bucket_name,ucfg.get('object-storage.quota'), minioClient) - - content_path = find_content_path("index.html") - - if(content_path): - logging.info(f"uploading example content to {bucket_name} from {content_path}") - res = minioClient.upload_folder_content(content_path,bucket_name) - else: - logging.warn("could not find example static content to upload") - - state['storage_route']=res - - if(len(bucket_policy_names)>0): - logging.info(f"granting rw access to created policies under namespace {namespace}") - minioClient.assign_rw_bucket_policy_to_user(namespace,bucket_policy_names) - - return state - -def delete_ow_storage(ucfg): - minioClient = mutil.MinioClient() - namespace = ucfg.get("namespace") - - if(ucfg.get('object-storage.data.enabled')): - bucket_name = ucfg.get('object-storage.data.bucket') - logging.info(f"*** removing private bucket {bucket_name} for {namespace}") - res = minioClient.force_bucket_remove(bucket_name) - - if(ucfg.get('object-storage.route.enabled')): - bucket_name = ucfg.get("object-storage.route.bucket") - logging.info(f"*** removing public bucket {bucket_name} for {namespace}") - res = minioClient.force_bucket_remove(bucket_name) - - return minioClient.delete_user(namespace) - -def delete_by_owner(): - spec = kus.build("minio") - res = kube.delete(spec) - logging.info(f"delete minio: {res}") - return res - -def delete_by_spec(): - spec = cfg.get("state.minio.spec") - res = False - if spec: - res = kube.delete(spec) - logging.info(f"delete minio: {res}") - return res - -def delete(owner=None): - data = util.get_minio_config_data() - minio_ingress.delete_minio_ingresses(data, owner) - - if owner: - return delete_by_owner() - else: - return delete_by_spec() - -def patch(status, action, owner=None): - """ - Called by the operator patcher to create/delete minio component - """ - try: - logging.info(f"*** handling request to {action} minio") - if action == 'create': - msg = create(owner) - operator_util.patch_operator_status(status,'minio','on') - else: - msg = delete(owner) - operator_util.patch_operator_status(status,'minio','off') - - logging.info(msg) - logging.info(f"*** hanlded request to {action} minio") - except Exception as e: - logging.error('*** failed to update minio: %s' % e) - operator_util.patch_operator_status(status,'minio','error') - -def patch_ingresses(status, action, owner=None): - """ - Called by the operator patcher to create/delete minio component - """ - try: - logging.info(f"*** handling request to {action} minio ingresses") - data = util.get_minio_config_data() - if action == 'update': - msg = minio_ingress.create_minio_ingresses(data, owner) - operator_util.patch_operator_status(status,'minio-ingresses','on') - - logging.info(msg) - logging.info(f"*** hanlded request to {action} minio ingresses") - except Exception as e: - logging.error('*** failed to update minio ingresses: %s' % e) - operator_util.patch_operator_status(status,'minio-ingresses','error') diff --git a/openserverless/minio_ingress.py b/openserverless/minio_ingress.py deleted file mode 100644 index 7317da1e..00000000 --- a/openserverless/minio_ingress.py +++ /dev/null @@ -1,226 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -import kopf, logging, time, os -import openserverless.kube as kube -import openserverless.kustomize as kus -import openserverless.config as cfg -import openserverless.util as util -import openserverless.apihost_util as apihost_util -import openserverless.endpoint as endpoint -import openserverless.openwhisk as openwhisk - -from openserverless.ingress_data import IngressData -from openserverless.route_data import RouteData - -def deploy_minio_route(apihost,namespace,type,service_name,port,context_path): - """ - Deploys a generic MINIO route ingress - param: apihost - param: namespace - param: type (s3, console) - param: service_name (normally it is minio) - param: port (9000 or 9090) - paramL context_path (/) - """ - route = RouteData(apihost) - route.with_route_name(endpoint.api_route_name(namespace,type)) - route.with_service_name(service_name) - route.with_service_kind("Service") - route.with_service_port(port) - route.with_context_path(context_path) - - logging.info(f"*** configuring minio route for service {service_name}:{port}") - path_to_template_yaml = route.render_template(namespace) - res = kube.kubectl("apply", "-f",path_to_template_yaml) - os.remove(path_to_template_yaml) - return res - -def deploy_minio_ingress(apihost, namespace, type, service_name,port,context_path): - """ - Deploys a generic MINIO nginx/traefik ingress - param: apihost - param: namespace - param: type (s3, console) - param: service_name (normally it is minio) - param: port (9000 or 9090) - paramL context_path (/) - """ - ingress = IngressData(apihost) - ingress.with_ingress_name(endpoint.api_ingress_name(namespace, type)) - ingress.with_secret_name(endpoint.ingress_secret_name(namespace, type)) - ingress.with_context_path(context_path) - ingress.with_service_name(service_name) - ingress.with_service_port(port) - - if ingress.requires_traefik_middleware(): - logging.info(f"*** configuring traefik middleware for {type} ingress") - path_to_template_yaml = ingress.render_traefik_middleware_template(namespace) - res = kube.kubectl("apply", "-f",path_to_template_yaml) - os.remove(path_to_template_yaml) - - logging.info(f"*** configuring static ingress for {type}") - path_to_template_yaml = ingress.render_template(namespace) - res = kube.kubectl("apply", "-f",path_to_template_yaml) - os.remove(path_to_template_yaml) - - return res - -def deploy_minio_upload_route(apihost,namespace, type): - upload = RouteData(apihost) - upload.with_route_name(endpoint.api_route_name(namespace,type)) - upload.with_service_name("openserverless-minio") - upload.with_service_kind("Service") - upload.with_service_port("9000") - upload.with_context_path("/api/upload") - upload.with_rewrite_target("/") - - logging.info("*** configuring route for upload") - path_to_template_yaml = upload.render_template(namespace) - res = kube.kubectl("apply", "-f",path_to_template_yaml) - os.remove(path_to_template_yaml) - return res - -def deploy_minio_upload_ingress(apihost, namespace, type): - upload = IngressData(apihost) - upload.with_ingress_name(endpoint.api_ingress_name(namespace,type)) - upload.with_secret_name(endpoint.api_secret_name(namespace)) - upload.with_context_path("/api/upload") - upload.with_context_regexp("(/|$)(.*)") - upload.with_rewrite_target("/$2") - upload.with_service_name("openserverless-minio") - upload.with_service_port("9000") - upload.with_middleware_ingress_name(endpoint.api_middleware_ingress_name(namespace,type)) - - if upload.requires_traefik_middleware(): - logging.info(f"*** configuring traefik middleware for {type} ingress") - path_to_template_yaml = upload.render_traefik_middleware_template(namespace) - res = kube.kubectl("apply", "-f",path_to_template_yaml) - os.remove(path_to_template_yaml) - - logging.info(f"*** configuring static ingress for {type} ingress") - path_to_template_yaml = upload.render_template(namespace) - res = kube.kubectl("apply", "-f",path_to_template_yaml) - os.remove(path_to_template_yaml) - - return res - -def create_api_uploader_endpoint(runtime, apihost, owner=None): - """ - exposes MINIO api in the uploader-api ingress/route - """ - if runtime == 'openshift': - return deploy_minio_upload_route(apihost,"openserverless","minio-upload") - else: - return deploy_minio_upload_ingress(apihost,"openserverless","minio-upload") - - -def create_s3_ingress_endpoint(data, runtime, apihost, owner=None): - """ - exposes MINIO S3 api ingress ingress/route - """ - if runtime == 'openshift': - return deploy_minio_route(apihost,"openserverless","minio-s3","openserverless-minio","9000","/") - else: - return deploy_minio_ingress(apihost,"openserverless","minio-s3","openserverless-minio","9000","/") - -def create_console_ingress_endpoint(data, runtime, apihost, owner=None): - """ - exposes MINIO api ingress ingress/route - """ - - if runtime == 'openshift': - return deploy_minio_route(apihost,"openserverless","minio-console","openserverless-minio","9090","/") - else: - return deploy_minio_ingress(apihost,"openserverless","minio-console","openserverless-minio","9090","/") - -def get_minio_ingress_hostname(runtime, apihost_url, prefix, hostname_from_config): - """ - Determine the minio ingress hostname. In auto mode the prefix is appended - to the configured apihost,, otherwise the one from configuration is used. - """ - if hostname_from_config in ["auto"]: - return apihost_util.append_prefix_to_url(apihost_url, prefix) - - return apihost_util.get_ingress_url(runtime, hostname_from_config) - - -def create_minio_ingresses(data, owner=None): - """ - Creates all the MINIO related ingresses according to provide configuration - """ - runtime = cfg.get('openserverless.kube') - apihost_url = apihost_util.get_apihost(runtime) - res = create_api_uploader_endpoint(runtime, apihost_url, owner) - - if data['minio_s3_ingress_enabled']: - s3_hostname_url = get_minio_ingress_hostname(runtime, apihost_url,"s3",data['minio_s3_ingress_hostname']) - res += create_s3_ingress_endpoint(data, runtime, s3_hostname_url, owner) - - if res: - openwhisk.annotate(f"s3_api_url={s3_hostname_url}") - - if data['minio_console_ingress_enabled']: - minio_hostname_url = get_minio_ingress_hostname(runtime, apihost_url,"minio",data['minio_console_ingress_hostname']) - res += create_console_ingress_endpoint(data, runtime, minio_hostname_url, owner) - - if res: - openwhisk.annotate(f"s3_console_url={minio_hostname_url}") - - # force he s3 API to be localhost:9000 on Kind based deployment. - if runtime in ["kind"]: - openwhisk.annotate(f"s3_api_url=http://localhost:9000") - - return res - - -def delete_minio_ingress(runtime, namespace, ingress_class, type, owner=None): - """ - undeploys ingresses for minio apihost - """ - logging.info(f"*** removing ingresses for minio upload") - - try: - res = "" - if(runtime=='openshift'): - res = kube.kubectl("delete", "route",endpoint.api_route_name(namespace,type)) - return res - - res += kube.kubectl("delete", "ingress",endpoint.api_ingress_name(namespace,type)) - - if(ingress_class == 'traefik'): - res = kube.kubectl("delete", util.get_traefik_middleware_resource(),endpoint.api_middleware_ingress_name(namespace,type)) - - return res - except Exception as e: - logging.warn(e) - return None - -def delete_minio_ingresses(data, owner=None): - namespace = "openserverless" - runtime = cfg.get('openserverless.kube') - ingress_class = util.get_ingress_class(runtime) - - res = delete_minio_ingress(runtime, namespace, ingress_class, "minio-upload", owner) - - if data['minio_s3_ingress_enabled']: - res += delete_minio_ingress(runtime, namespace, ingress_class, "minio-s3", owner) - - if data['minio_console_ingress_enabled']: - res += delete_minio_ingress(runtime, namespace, ingress_class, "minio-console", owner) - - return res diff --git a/openserverless/minio_util.py b/openserverless/minio_util.py deleted file mode 100644 index 7cd242a7..00000000 --- a/openserverless/minio_util.py +++ /dev/null @@ -1,179 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -# this module wraps mc minio client using admin credentials -# to perform various operations - -import logging -import json -import subprocess -import openserverless.config as cfg -import openserverless.template as ntp -import openserverless.util as util -import os - -class MinioClient: - - def __init__(self): - self.minio_api_host = cfg.get("minio.host", "MINIO_API_HOST", "openserverless-minio") - self.minio_api_port = cfg.get("9000", "MINIO_API_PORT", "9000") - self.admin_username = cfg.get("minio.admin.user", "MINIO_ADMIN_USER", "minioadmin") - self.admin_password = cfg.get("minio.admin.password", "MINIO_ADMIN_PASSWORD", "minioadmin") - self.minio_api_url = f"http://{self.minio_api_host}:{self.minio_api_port}" - self.alias = "local" - - # automatically adds the local alias to mc configuration to operate on openserverless main MINIO server - self.mc("alias","set", self.alias, self.minio_api_url, self.admin_username, self.admin_password) - - # execute minio commands using the mc cli tools installed by default inside the operator - def mc(self, *kwargs): - cmd = ["mc"] - cmd += list(kwargs) - - # executing - logging.debug(cmd) - try: - res = subprocess.run(cmd, capture_output=True) - - returncode = res.returncode - output = res.stdout.decode() - error = res.stderr.decode() - - if returncode != 0: - self.last_output = error - logging.error(error) - else: - self.last_output = output - logging.info(output) - - return returncode == 0 - except Exception as e: - logging.error(e) - return e - - def get_last_output(self): - """ - returns the last output of the executed command - """ - return self.last_output if hasattr(self, 'last_output') else None - - - def add_user(self, username, secret_key): - """ - adds a new minio user to the configured minio instance - """ - res = util.check(self.mc("admin","user","add", self.alias, username, secret_key),"add_user",True) - return util.check(self.init_namespace_alias(username, secret_key),"init_namespace_alias",res) - - def remove_user(self, username): - """ - removes a minio user to the configured minio instance - """ - res = util.check(self.mc("admin","user","remove", self.alias, username),"remove_user",True) - return util.check(self.remove_namespace_alias(username),"remove_namespace_alias",res) - - def make_bucket(self, bucket_name): - """ - adds a new bucket inside the configured minio instance - """ - return util.check(self.mc("mb",f"{self.alias}/{bucket_name}"),"make_bucket",True) - - def force_bucket_remove(self, bucket_name): - """ - removes unconditionally a bucket - """ - return util.check(self.mc("rb","--force",f"{self.alias}/{bucket_name}"),"force_bucket_remove",True) - - def make_public_bucket(self, bucket_name): - """ - adds a new public bucket to the configured minio instance - """ - res = util.check(self.make_bucket(bucket_name),"make_bucket",True) - return util.check(self.mc("anonymous","-r","set","download",f"{self.alias}/{bucket_name}"),"make_public_bucket",res) - - def assign_quota_to_bucket(self, bucket_name, quota): - """ - assign the specified quota on the given bucket - """ - return util.check(self.mc("quota","set",f"{self.alias}/{bucket_name}","--size", f"{quota}m"),"assign_quota_to_bucket",True) - - - def assign_policy_to_user(self, username, policy): - """ - assign the specified policy to the given username - """ - return util.check(self.mc("admin","policy","attach",self.alias,policy,"--user", username),"assign_policy_to_user",True) - - def add_policy(self, policy, path_to_policy_json): - """ - add a new policy into minio - """ - return util.check(self.mc("admin","policy","create",self.alias,policy,path_to_policy_json),"add_policy",True) - - def remove_policy(self, policy): - """ - add a new policy into minio - """ - return util.check(self.mc("admin","policy","remove",self.alias,policy),"remove_policy",True) - - def render_policy(self,username,template,data): - """ - uses the given template policy to render a final policy and returns the absolute path to rendered policy file. - """ - out = f"/tmp/__{username}_{template}" - file = ntp.spool_template(template, out, data) - return os.path.abspath(file) - - def assign_rw_bucket_policy_to_user(self,username,bucket_names): - """ - defines a rw policy template for the specified bucket and assigns it to the given username. - """ - policy_name = f"{username}_rw_policy" - path_to_policy_json = self.render_policy(username,"minio_rw_policy_tpl.json",{"bucket_arns":bucket_names}) - res=util.check(self.add_policy(policy_name,path_to_policy_json),"add_policy",True) - res=util.check(self.assign_policy_to_user(username,policy_name),"assign_rw_bucket_policy_to_user",res) - os.remove(path_to_policy_json) - return res - - def delete_user(self,username): - """ - removes the user and the corresponding policy - """ - policy_name = f"{username}_rw_policy" - res=util.check(self.remove_user(username),"removed_user",True) - return util.check(self.remove_policy(policy_name),"deleted_user_policy",res) - - def init_namespace_alias(self, namespace, secret_key): - """ - called to initialize a local minio alias for a namespace user (used to impersonate the user uploading content) - """ - return util.check(self.mc("alias","set", f"local_{namespace}", self.minio_api_url, namespace, secret_key),"init_namespace_alias_call",True) - - def remove_namespace_alias(self, namespace): - """ - called to initialize to remove a local minio alias for a namespace user - """ - return util.check(self.mc("alias","remove",f"local_{namespace}"),"remove_namespace_alias_call",True) - - def upload_folder_content(self,origin,bucket): - """ - uploads the given content using a local alias for the corresponding namespace - """ - return util.check(self.mc("cp","-r",origin,f"{self.alias}/{bucket}"),"upload_folder_content",True) - - - diff --git a/openserverless/mongodb.py b/openserverless/mongodb.py deleted file mode 100644 index a308e281..00000000 --- a/openserverless/mongodb.py +++ /dev/null @@ -1,203 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -# -# Deploys mongodb for openserverless using operator or standalone -# implementation. -# -# By default standalone configuration is used unless mongodb.useOperator is set to true -# -import openserverless.config as cfg -import openserverless.util as util -import openserverless.mongodb_operator as operator -import openserverless.mongodb_standalone as standalone -import openserverless.kube as kube -import openserverless.template as ntp -import logging, json -import os -import urllib.parse -import openserverless.openwhisk as openwhisk - -from openserverless.user_config import UserConfig -from openserverless.user_metadata import UserMetadata - -def get_mdb_pod_name(): - useOperator = cfg.get('mongodb.useOperator') or False - pod_name_jsonpath = useOperator and "{.items[?(@.metadata.labels.app == 'openserverless-mongodb-svc')].metadata.name}" or "{.items[?(@.metadata.labels.app == 'openserverless-mongodb')].metadata.name}" - return util.get_pod_name(pod_name_jsonpath) - -def _add_mdb_user_metadata(ucfg, user_metadata): - """ - adds an entry for the mongodb connectivity, i.e - something like "mongodb://{namespace}:{auth}@openserverless-mongodb-0.openserverless-mongodb-svc.openserverless.svc.cluster.local:27017/{database}?connectTimeoutMS=60000"} - """ - - try: - mdb_service = util.get_service("{.items[?(@.metadata.name == 'openserverless-mongodb-svc')]}") - - if(mdb_service): - mdb_service_name = mdb_service['metadata']['name'] - mdb_ns = mdb_service['metadata']['namespace'] - mdb_pod_name = get_mdb_pod_name() - - username = urllib.parse.quote(ucfg.get('namespace')) - password = urllib.parse.quote(ucfg.get('mongodb.password')) - auth = f"{username}:{password}" - database = ucfg.get('mongodb.database') - - mdb_url = f"mongodb://{auth}@{mdb_pod_name}.{mdb_service_name}.{mdb_ns}.svc.cluster.local:27017/{database}?connectTimeoutMS=60000" - user_metadata.add_metadata("MONGODB_URL",mdb_url) - return None - except Exception as e: - logging.error(f"failed to build mongodb_url for {ucfg.get('mongodb.database')}: {e}") - return None - -def create(owner=None): - """ - Deploys the mongodb operator and wait for the operator to be ready. - """ - useOperator = cfg.get('mongodb.useOperator') or False - - res = useOperator and operator.create(owner) or standalone.create(owner) - - if(res): - update_system_cm_for_mdb() - - return res - -def update_system_cm_for_mdb(): - logging.info("*** annotating configuration for mongodb openserverless user") - try: - mdb_service = util.get_service("{.items[?(@.metadata.name == 'openserverless-mongodb-svc')]}") - if(mdb_service): - mdb_pod_name = get_mdb_pod_name() - mdb_service_name = mdb_service['metadata']['name'] - mdb_ns = mdb_service['metadata']['namespace'] - - data = util.get_mongodb_config_data() - - username = urllib.parse.quote(data['mongo_openserverless_user']) - password = urllib.parse.quote(data['mongo_openserverless_password']) - auth = f"{username}:{password}" - - mdb_url = f"mongodb://{auth}@{mdb_pod_name}.{mdb_service_name}.{mdb_ns}.svc.cluster.local:27017/openserverless?connectTimeoutMS=60000" - openwhisk.annotate(f"mongodb_url={mdb_url}") - logging.info("*** saved annotation for mongodb openserverless user") - except Exception as e: - logging.error(f"failed to build mongodb_url for openserverless database: {e}") - -def delete(owner=None): - useOperator = cfg.get('mongodb.useOperator') or False - - if useOperator: - return operator.delete(owner) - - return standalone.delete(owner) - -def init(): - return "TODO" - -def render_mongodb_script(namespace,template,data): - """ - uses the given template to render a js script to execute as a json. - """ - if not util.validate_namespace(namespace): - raise ValueError(f"Invalid namespace {namespace}") - out = f"/tmp/__{namespace}_{template}" - file = ntp.spool_template(template, out, data) - return os.path.abspath(file) - -def exec_mongosh_command(pod_name,path_to_mdb_script): - if not os.path.exists(path_to_mdb_script): - raise ValueError(f"invalid path script in exec_mongosh_command") - logging.info(f"passing script {path_to_mdb_script} to pod {pod_name}") - res = kube.kubectl("cp",path_to_mdb_script,f"{pod_name}:{path_to_mdb_script}") - res = kube.kubectl("exec","-it",pod_name,"--","/bin/bash","-c",f"mongosh --file {path_to_mdb_script}") - os.remove(path_to_mdb_script) - return res - -def create_db_user(ucfg: UserConfig, user_metadata: UserMetadata): - database = ucfg.get('mongodb.database') - logging.info(f"authorizing new mongodb database {database}") - - if not util.validate_database_name(database): - logging.error(f"failed to add Mongodb database: invalid database name {database}") - return None - - try: - data = util.get_mongodb_config_data() - data["database"]=database - data["subject"]=ucfg.get('namespace') - data["auth"]=ucfg.get('mongodb.password') - data["mode"]="create" - - path_to_mdb_script = render_mongodb_script(ucfg.get('namespace'),"mongodb_manage_user_tpl.js",data) - pod_name = util.get_pod_name("{.items[?(@.metadata.labels.app == 'openserverless-mongodb')].metadata.name}") - - if(pod_name): - res = exec_mongosh_command(pod_name,path_to_mdb_script) - _add_mdb_user_metadata(ucfg, user_metadata) - return res - - return None - except Exception as e: - logging.error(f"failed to add Mongodb database {database}: {e}") - return None - -def delete_db_user(namespace, database): - logging.info(f"removing mongodb database {database}") - - if not util.validate_database_name(database): - logging.error(f"failed to remove Mongodb database: invalid database name {database}") - return None - - try: - data = util.get_mongodb_config_data() - data["subject"]=namespace - data["database"]=database - data["mode"]="delete" - - path_to_mdb_script = render_mongodb_script(namespace,"mongodb_manage_user_tpl.js",data) - pod_name = util.get_pod_name("{.items[?(@.metadata.labels.app == 'openserverless-mongodb')].metadata.name}") - - if(pod_name): - res = exec_mongosh_command(pod_name,path_to_mdb_script) - return res - - return None - except Exception as e: - logging.error(f"failed to remove Mongodb database {namespace} authorization id and key: {e}") - return None - -def patch(status, action, owner=None): - """ - Called the the operator patcher to create/delete mongodb - """ - try: - logging.info(f"*** handling request to {action} mongodb") - if action == 'create': - msg = create(owner) - status['whisk_create']['mongodb']='on' - else: - msg = delete(owner) - status['whisk_create']['mongodb']='off' - - logging.info(msg) - logging.info(f"*** hanlded request to {action} mongodb") - except Exception as e: - logging.error('*** failed to update mongodb: %s' % e) - status['whisk_create']['mongodb']='error' \ No newline at end of file diff --git a/openserverless/mongodb_operator.py b/openserverless/mongodb_operator.py deleted file mode 100644 index b49a3ebf..00000000 --- a/openserverless/mongodb_operator.py +++ /dev/null @@ -1,135 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# -# Containers can get the openserverless mongodb connection string -# deployed from the openserverless-operator using something similar to -#containers: -# - name: test-app -# env: -# - name: "CONNECTION_STRING" -# valueFrom: -# secretKeyRef: -# name: openserverless-mongodb-openserverless-openserverless -# key: connectionString.standardSrv -# -# WARNING connectionString.standardSrv it is normally a base64 endoded string -# -# Currently for development purposes it is possible to access mongodb by port forwarding -# kubectl -n openserverless port-forward service/openserverless-mongodb-svc 27017:27017 -# - -import kopf, json, time -import openserverless.kube as kube -import openserverless.kustomize as kus -import openserverless.config as cfg -import openserverless.util as util -import logging - -def get_operator_pod_name(): - pods = kube.get_pods("name=mongodb-kubernetes-operator") - items = list(pods['items']) - - if(len(items)): - item = items[0] - return item['metadata']['name'] - - return None - -def create(owner=None): - """ - Deploys the mongodb operator and wait for the operator to be ready. - """ - logging.info("*** creating mongodb-operator") - admin_user = cfg.get('mongodb.admin.user') or "whisk_user" - admin_pwd = cfg.get('mongodb.admin.password') or "0therPa55" - nuv_user = cfg.get('mongodb.openserverless.user') or "openserverless" - nuv_pwd = cfg.get('mongodb.openserverless.password') or "s0meP@ass3" - exposed = cfg.get('mongodb.exposedExternally') or False - - data = util.get_mongodb_config_data() - - spec = kus.kustom_list("mongodb-operator") - - if owner: - kopf.append_owner_reference(spec['items'], owner) - else: - cfg.put("state.mongodb-operator.spec", spec) - - res = kube.apply(spec) - logging.info("*** created mongodb operator") - - pod_name = get_operator_pod_name() - #wait for mongodb_operator to be ready - - if( pod_name ): - logging.info(f"checking for {pod_name}") - while not kube.wait(f"pod/{pod_name}", "condition=ready"): - logging.info(f"waiting for {pod_name} to be ready...") - time.sleep(1) - - logging.info("*** creating a mongodb instance") - - tpl_filter = ["mongodb-auth.yaml","mongodb-auth-openserverless.yaml","mongodb.yaml"] - if exposed: - logging.info("*** including mongodb service for localhost access") - tpl_filter.append("mongodb-svc.yaml") - - - mkust = kus.patchTemplates("mongodb-operator-deploy", ["mongodb-auth.yaml","mongodb-auth-openserverless.yaml","mongodb-config.yaml"], data) - mspec = kus.restricted_kustom_list("mongodb-operator-deploy", mkust, templates=[],templates_filter=tpl_filter, data=data) - - if owner: - kopf.append_owner_reference(mspec['items'], owner) - else: - cfg.put("state.mongodb.spec", mspec) - - res = kube.apply(mspec) - # dynamically detect mongodb pod and wait for readiness - util.wait_for_pod_ready("{.items[?(@.metadata.labels.app == 'openserverless-mongodb-svc')].metadata.name}") - else: - logging.info("*** something went wrong deploying mongodb operator") - return res - - -def delete_by_owner(): - spec = kus.build("mongodb-operator-deploy") - res = kube.delete(spec) - spec = kus.build("mongodb-operator") - res = kube.delete(spec) - logging.info(f"delete mongodb: {res}") - return res - -def delete_by_spec(): - spec = cfg.get("state.mongodb.spec") - res = False - if spec: - res = kube.delete(spec) - logging.info(f"delete mongodb: {res}") - - spec = cfg.get("state.mongodb-operator.spec") - if spec: - res = kube.delete(spec) - logging.info(f"delete mongodb-operator: {res}") - return res - -def delete(owner=None): - if owner: - return delete_by_owner() - else: - return delete_by_spec() \ No newline at end of file diff --git a/openserverless/mongodb_standalone.py b/openserverless/mongodb_standalone.py deleted file mode 100644 index b0589b42..00000000 --- a/openserverless/mongodb_standalone.py +++ /dev/null @@ -1,73 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -# -# Deploys a standalone mongodb -# - -import kopf, json, time -import openserverless.kube as kube -import openserverless.kustomize as kus -import openserverless.config as cfg -import openserverless.util as util -import logging - -def create(owner=None): - """ - Deploys the mongodb operator and wait for the operator to be ready. - """ - logging.info("*** creating mongodb-standalone") - exposed = cfg.get('mongodb.exposedExternally') or False - - data = util.get_mongodb_config_data() - mkust = kus.patchTemplates("mongodb-standalone", ["mongodb-auth.yaml","mongodb-cm.yaml","mongodb-sts.yaml"], data) - mkust += kus.patchPersistentVolumeClaim("mongodb-data","/spec/resources/requests/storage",f"{data['size']}Gi") - mspec = kus.kustom_list("mongodb-standalone", mkust, templates=[], data=data) - - if owner: - kopf.append_owner_reference(mspec['items'], owner) - else: - cfg.put("state.mongodb.spec", mspec) - - res = kube.apply(mspec) - - # dynamically detect mongodb pod and wait for readiness - util.wait_for_pod_ready("{.items[?(@.metadata.labels.app == 'openserverless-mongodb')].metadata.name}") - - logging.info("*** created mongodb-standalone") - return res - -def delete_by_owner(): - spec = kus.build("mongodb-standalone") - res = kube.delete(spec) - logging.info(f"delete mongodb: {res}") - return res - -def delete_by_spec(): - spec = cfg.get("state.mongodb.spec") - res = False - if spec: - res = kube.delete(spec) - logging.info(f"delete mongodb: {res}") - - return res - -def delete(owner=None): - if owner: - return delete_by_owner() - else: - return delete_by_spec() \ No newline at end of file diff --git a/openserverless/monitoring.py b/openserverless/monitoring.py index 5d820e4e..80b742e2 100644 --- a/openserverless/monitoring.py +++ b/openserverless/monitoring.py @@ -16,7 +16,7 @@ # under the License. # # -# Deploys a standalone mongodb +# Deploys the monitoring stack # import kopf diff --git a/openserverless/patcher.py b/openserverless/patcher.py index 6b2d189d..7c178d3d 100644 --- a/openserverless/patcher.py +++ b/openserverless/patcher.py @@ -17,10 +17,9 @@ # import logging, time import openserverless.openwhisk as openwhisk -import openserverless.ferretdb as mongodb +import openserverless.ferretdb as ferretdb import openserverless.redis as redis import openserverless.cronjob as cron -import openserverless.minio_deploy as minio import openserverless.storage_static as static import openserverless.config as cfg import openserverless.kube as kube @@ -130,7 +129,7 @@ def patch(diff, status, owner=None, name=None): components_updated = True if "mongodb" in what_to_do: - mongodb.patch(status,what_to_do['mongodb'], owner) + ferretdb.patch(status,what_to_do['mongodb'], owner) components_updated = True if "redis" in what_to_do: @@ -141,10 +140,6 @@ def patch(diff, status, owner=None, name=None): cron.patch(status,what_to_do['cron'], owner) components_updated = True - if "minio" in what_to_do: - minio.patch(status,what_to_do['minio'], owner) - components_updated = True - if "static" in what_to_do: static.patch(status,what_to_do['static'], owner) components_updated = True @@ -175,9 +170,6 @@ def patch(diff, status, owner=None, name=None): issuer.patch(status,what_to_do['endpoint'], owner) endpoint.patch(status,what_to_do['endpoint'], owner) - if "minio-ingresses" in what_to_do and what_to_do['minio-ingresses'] == "update": - minio.patch_ingresses(status,what_to_do['minio-ingresses'], owner) - if "seaweedfs-ingresses" in what_to_do and what_to_do['seaweedfs-ingresses'] == "update": seaweedfs.patch_ingresses(status,what_to_do['seaweedfs-ingresses'], owner) diff --git a/openserverless/policies/minio_rw_policy_tpl.json b/openserverless/policies/minio_rw_policy_tpl.json deleted file mode 100644 index 34150cf0..00000000 --- a/openserverless/policies/minio_rw_policy_tpl.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "s3:*" - ], - "Effect": "Allow", - "Resource": [ - {% for bucket_arn in bucket_arns %}"arn:aws:s3:::{{ bucket_arn }}"{{ "," if not loop.last }}{% endfor %} - ] - } - ] -} \ No newline at end of file diff --git a/openserverless/redis.py b/openserverless/redis.py index b957b45b..86799b8f 100644 --- a/openserverless/redis.py +++ b/openserverless/redis.py @@ -23,6 +23,11 @@ import openserverless.util as util import openserverless.openwhisk as openwhisk import urllib.parse +import hashlib +import contextlib +import redis as redislib +from openserverless.redis_acl import tenant_rules +from openserverless.datastore_connection import service_endpoint import os, os.path import logging, json import kopf @@ -43,8 +48,8 @@ def _add_redis_user_metadata(ucfg: UserConfig, user_metadata:UserMetadata): if(redis_service): redis_service_name = redis_service['metadata']['name'] redis_service_port = redis_service['spec']['ports'][0]['port'] - username = urllib.parse.quote(ucfg.get('namespace')) - password = urllib.parse.quote(ucfg.get('redis.password')) + username = urllib.parse.quote(ucfg.get('namespace'), safe='') + password = urllib.parse.quote(ucfg.get('redis.password'), safe='') auth = f"{username}:{password}" redis_url = f"redis://{auth}@{redis_service_name}:{redis_service_port}" redis_alt_url = f"redis://{password}@{redis_service_name}:{redis_service_port}" @@ -98,6 +103,7 @@ def create_openserverless_db_user(data): logging.info(f"authorizing redis for namespace openserverless") try: data['mode']="create" + data['redis_system_account']=True path_to_script = render_redis_script(data['namespace'],"redis_manage_user_tpl.txt",data) pod_name = util.get_pod_name("{.items[?(@.metadata.labels.name == 'redis')].metadata.name}") @@ -171,63 +177,112 @@ def exec_redis_command(pod_name,path_to_script): os.remove(path_to_script) return res +def admin_client(password=None): + stored = util.get_redis_config_data()['redis_password'] + if password is not None and password != stored: + raise RuntimeError('Redis administrator credentials do not match') + + # doctest imports this file with openserverless/ on sys.path, where the + # module name redis shadows the third-party driver. Resolve the class only + # when an actual client is requested, preserving import-only tooling. + class _AdminClient(redislib.Redis): + def __init__(self, secret): + self._service_context = contextlib.ExitStack() + try: + host, port = self._service_context.enter_context(service_endpoint('redis', 6379)) + super().__init__(host=host, port=port, password=secret, decode_responses=True, + socket_timeout=10, socket_connect_timeout=5) + except BaseException: + self._service_context.close() + raise + + def close(self): + try: + if hasattr(self, 'connection_pool'): + super().close() + finally: + self._service_context.close() + + return _AdminClient(stored) + + +def _tenant_identity(namespace, prefix=None): + if (not util.validate_namespace(namespace) + or namespace in ('default', 'openserverless')): + raise ValueError('Invalid Redis tenant name') + if prefix is not None: + if not isinstance(prefix, str) or not prefix: + raise ValueError('Invalid Redis tenant prefix') + prefix = prefix if prefix.endswith(':') else prefix + ':' + tenant_rules(prefix) + return prefix + + +def _save_acl_if_configured(client): + # Preserve the 0.9.0 storage contract: persist when an ACL file is configured. + # With no ACL file, startup reconciliation recreates identities as before. + if client.config_get('aclfile').get('aclfile'): + client.acl_save() + + +def provision_user(namespace, password, prefix, readonly=False): + prefix = _tenant_identity(namespace, prefix) + if not isinstance(password, str) or not password: + raise ValueError('Redis tenant password is required') + hashed = hashlib.sha256(password.encode()).hexdigest() + with admin_client() as client, client.lock('__ops_acl_lock__', timeout=120, blocking_timeout=15): + previous = client.acl_getuser(namespace) + # 0.9.0 reconciliation adds the configured password without revoking + # existing valid credentials. Change only authorization in this backport. + hashes = list(dict.fromkeys((previous or {}).get('passwords', []) + [hashed])) + client.execute_command('ACL', 'SETUSER', namespace, 'reset', 'on', + *['#' + value for value in hashes], *tenant_rules(prefix, readonly)) + try: + _save_acl_if_configured(client) + except redislib.RedisError: + client.acl_deluser(namespace) + raise RuntimeError('Could not persist Redis tenant ACL') from None + return True + + +def set_user_readonly(namespace, prefix, readonly): + prefix = _tenant_identity(namespace, prefix) + with admin_client() as client, client.lock('__ops_acl_lock__', timeout=120, blocking_timeout=15): + if client.acl_getuser(namespace) is None: + raise RuntimeError('Redis tenant does not exist') + # Keep credentials; replace every command, key, channel and selector grant. + client.execute_command('ACL', 'SETUSER', namespace, *tenant_rules(prefix, readonly)) + _save_acl_if_configured(client) + return True + + def create_db_user(ucfg: UserConfig, user_metadata: UserMetadata, read_only_mode = False): logging.info(f"authorizing new redis namespace {ucfg.get('namespace')}") try: wait_for_redis_ready() - data = util.get_redis_config_data() - - # if prefix not provided defaults to user namespace - prefix = ucfg.get('redis.prefix') or ucfg.get('namespace') - if(not prefix.endswith(":")): - prefix = f"{prefix}:" - data['prefix']=prefix - data['namespace']=ucfg.get('namespace') - data['password']=ucfg.get('redis.password') - data['mode']="create" - - if read_only_mode: - logging.warn(f"activating {prefix} in read-only mode") - data['mode']="create_readonly" - - path_to_script = render_redis_script(ucfg.get('namespace'),"redis_manage_user_tpl.txt",data) - pod_name = util.get_pod_name("{.items[?(@.metadata.labels.name == 'redis')].metadata.name}") + prefix = _tenant_identity(ucfg.get('namespace'), ucfg.get('redis.prefix') or ucfg.get('namespace')) + provision_user(ucfg.get('namespace'), ucfg.get('redis.password'), prefix, read_only_mode) + user_metadata.add_metadata('REDIS_PREFIX', prefix) + _add_redis_user_metadata(ucfg, user_metadata) + return True - if(pod_name): - res = exec_redis_command(pod_name,path_to_script) - - if res: - user_metadata.add_metadata("REDIS_PREFIX",prefix) - _add_redis_user_metadata(ucfg, user_metadata) - return res - else: - logging.error(f"failed to add redis namespace {ucfg.get('namespace')}") - - return None except Exception as e: logging.error(f"failed to add redis namespace {ucfg.get('namespace')}: {e}") return None def delete_db_user(namespace): - logging.info(f"removing redis namespace {namespace}") - - try: - data = util.get_redis_config_data() - data["namespace"]=namespace - data["mode"]="delete" - - path_to_script = render_redis_script(namespace,"redis_manage_user_tpl.txt",data) - pod_name = util.get_pod_name("{.items[?(@.metadata.labels.name == 'redis')].metadata.name}") - - if(pod_name): - res = exec_redis_command(pod_name,path_to_script) - return res - - return None - except Exception as e: - logging.error(f"failed to remove redis namespace {namespace}: {e}") + _tenant_identity(namespace) + try: + with admin_client() as client, client.lock('__ops_acl_lock__', timeout=120, blocking_timeout=15): + client.acl_deluser(namespace) + _save_acl_if_configured(client) + # Preserve the existing contract: deleting credentials retains the data. + return True + except Exception: + logging.error('Failed to remove Redis tenant credentials') return None + def patch(status, action, owner=None): """ Called the the operator patcher to create/delete redis diff --git a/openserverless/redis_acl.py b/openserverless/redis_acl.py new file mode 100644 index 00000000..8ae0a8dc --- /dev/null +++ b/openserverless/redis_acl.py @@ -0,0 +1,37 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import re + + +def tenant_rules(prefix, readonly=False): + """Replace tenant grants, including legacy key/channel grants and selectors.""" + if not isinstance(prefix, str) or not re.fullmatch(r'[A-Za-z0-9_.:/-]{1,200}:', prefix): + raise ValueError('Invalid Redis tenant prefix') + rules = ['-@all', 'resetkeys', 'resetchannels', 'clearselectors', '+@read', '+@transaction'] + if readonly: + rules += ['+del', '+unlink', '+subscribe', '+psubscribe', '+unsubscribe', + '+punsubscribe', '+ssubscribe', '+sunsubscribe'] + else: + rules += ['+@write', '+@pubsub'] + rules += ['-@admin', '-@dangerous', '-@scripting', '-keys', '-scan', '-dbsize', + '-randomkey', '-sort', '-sort_ro', '-pubsub', '+ping', '+echo', '+hello', + '+auth', '+quit', '+client|setname', '+client|setinfo', '+client|id', + '~' + prefix + '*', '&' + prefix + '*', + '+eval', '+evalsha', '+eval_ro', '+evalsha_ro', '+script|load', '+script|exists'] + return rules diff --git a/openserverless/redis_client.py b/openserverless/redis_client.py index cc619576..9f9695d8 100644 --- a/openserverless/redis_client.py +++ b/openserverless/redis_client.py @@ -85,46 +85,9 @@ def calculate_prefix_allocated_size(self, prefix): return None def set_prefix_readonly(self, namespace, prefix): - """ - Submit the redis script to revoke namespace write rights on the given prefix - """ - self._data['prefix']=prefix - self._data['namespace']=namespace - self._data['mode']='readonly' - logging.info("setting redis/valkey %s with @READ ACL", prefix) - - try: - redis.wait_for_redis_ready() - path_to_script = redis.render_redis_script(namespace,"redis_manage_user_tpl.txt",self._data) - pod_name = util.get_pod_name("{.items[?(@.metadata.labels.name == 'redis')].metadata.name}") - - if(pod_name): - res = redis.exec_redis_command(pod_name,path_to_script) - return res - return None - except Exception as e: - logging.error("failed to set redis/valkey %s with @READ ACL: %s", prefix, e) - return None + """Apply the shared tenant policy while preserving credentials and Lua reads.""" + return redis.set_user_readonly(namespace, prefix, True) def set_prefix_all(self, namespace, prefix): - """ - Submit the redis script to re-enable namespace write rights on the given prefix - """ - - self._data['prefix']=prefix - self._data['namespace']=namespace - self._data['mode']='readandwrite' - logging.info("setting redis/valkey %s with +@ALL ACL", prefix) - - try: - redis.wait_for_redis_ready() - path_to_script = redis.render_redis_script(namespace,"redis_manage_user_tpl.txt",self._data) - pod_name = util.get_pod_name("{.items[?(@.metadata.labels.name == 'redis')].metadata.name}") - - if(pod_name): - res = redis.exec_redis_command(pod_name,path_to_script) - return res - return None - except Exception as e: - logging.error("failed to set redis/valkey %s with +@ALL ACL: %s", prefix, e) - return None \ No newline at end of file + """Restore tenant data writes without restoring administrative commands.""" + return redis.set_user_readonly(namespace, prefix, False) diff --git a/openserverless/registry_deploy.py b/openserverless/registry_deploy.py index 833fe76e..fbe6dda4 100644 --- a/openserverless/registry_deploy.py +++ b/openserverless/registry_deploy.py @@ -176,11 +176,11 @@ def deploy_registry_route(data, namespace="openserverless"): def deploy_registry_ingress(data, namespace="openserverless"): """ - Deploys a generic MINIO nginx/traefik ingress + Deploys a generic nginx/traefik ingress param: apihost param: namespace param: type (s3, console) - param: service_name (normally it is minio) + param: service_name (normally it is seaweedfs) param: port (9000 or 9090) paramL context_path (/) """ @@ -285,5 +285,5 @@ def patch_ingresses(status, action, owner=None): logging.info(msg) logging.info(f"*** hanlded request to {action} registry ingresses") except Exception as e: - logging.error('*** failed to update minio ingresses: %s' % e) + logging.error('*** failed to update registry ingresses: %s' % e) operator_util.patch_operator_status(status,'registry-ingresses','error') diff --git a/openserverless/s3_bucket_policy.py b/openserverless/s3_bucket_policy.py index a3c7dcdd..366b69e4 100644 --- a/openserverless/s3_bucket_policy.py +++ b/openserverless/s3_bucket_policy.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. # -# this module wraps mc minio client using admin credentials +# this module wraps the minio S3 client SDK using admin credentials # to perform various operations import json diff --git a/openserverless/s3_client.py b/openserverless/s3_client.py index 4a5f3755..c6705102 100644 --- a/openserverless/s3_client.py +++ b/openserverless/s3_client.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. # -# this module wraps mc minio client using admin credentials +# this module wraps the minio S3 client SDK using admin credentials # to perform various operations import os diff --git a/openserverless/seaweedfs_deploy.py b/openserverless/seaweedfs_deploy.py index 4a598cda..d0f9c1ca 100644 --- a/openserverless/seaweedfs_deploy.py +++ b/openserverless/seaweedfs_deploy.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. # -import kopf, logging, time, os +import kopf, logging, time, os, json import openserverless.kube as kube import openserverless.kustomize as kus import openserverless.config as cfg @@ -94,8 +94,8 @@ def create(owner=None): def _annotate_nuv_metadata(data): """ - annotate openserverless configmap with entries for minio connectivity S3_ENDPOINT, S3_PORT, S3_ACCESS_KEY, S3_SECRET_KEY - this is becasue MINIO + annotate openserverless configmap with entries for S3 connectivity S3_ENDPOINT, S3_PORT, S3_ACCESS_KEY, S3_SECRET_KEY + this is because SEAWEEDFS is the S3 provider """ try: seaweed_service = _get_seaweedfs_service() @@ -114,23 +114,50 @@ def _annotate_nuv_metadata(data): openwhisk.annotate(f"s3_port={port['port']}") return None except Exception as e: - logging.error(f"failed to build minio_host for openserverless: {e}") + logging.error(f"failed to build seaweedfs host for openserverless: {e}") return None +def _configured_storage_buckets(): + """Read public and private bucket names from tenant configuration.""" + users = json.loads(kube.kubectl("get", "whisksusers", "-o", "json")) + web_buckets = {"openserverless-web"} + private_buckets = {"openserverless-data"} + for user in users.get("items", []): + storage = user.get("spec", {}).get("object-storage", {}) + if storage.get("data", {}).get("enabled"): + private_buckets.add(storage["data"]["bucket"]) + route = storage.get("route", {}) + if route.get("enabled") and not user.get("metadata", {}).get("deletionTimestamp"): + web_buckets.add(route["bucket"]) + return web_buckets, private_buckets + +def reconcile_anonymous_web_access(client): + """Replace all legacy anonymous grants with configured public buckets.""" + web_buckets, private_buckets = _configured_storage_buckets() + # Delete the identity before adding grants: legacy configurations may + # contain both an unscoped grant and stale bucket-scoped grants. + if not client.delete_user("anonymous"): + raise RuntimeError("Could not revoke existing anonymous S3 grants") + safe_web_buckets = web_buckets - private_buckets + if not client.add_anonymous_access(safe_web_buckets): + raise RuntimeError("Could not grant public access to web buckets") + if web_buckets & private_buckets: + raise ValueError("Public and private S3 bucket names overlap") + def create_seaweedfs_nuv_storage(data): """ Creates openserverless SEAWEEDFS custom resources """ logging.info("*** configuring SEAWEEDFS storage for openserverless") seaweedsfsClient = SeaweedfsClient() - + res = seaweedsfsClient.make_bucket("openserverless-data",data["default_bucket_quota"]) if res: openwhisk.annotate("s3_bucket_data=openserverless-data") - res = seaweedsfsClient.add_anonymous_access() res = seaweedsfsClient.make_bucket("openserverless-web",data["default_bucket_quota"]) res = seaweedsfsClient.make_public_bucket("openserverless-web") + reconcile_anonymous_web_access(seaweedsfsClient) if res: openwhisk.annotate("s3_bucket_static=openserverless-web") @@ -153,6 +180,15 @@ def create_ow_storage(state, ucfg: UserConfig, user_metadata: UserMetadata, owne namespace = ucfg.get("namespace") secretkey = ucfg.get("object-storage.password") + if ucfg.get('object-storage.route.enabled') or ucfg.get('object-storage.data.enabled'): + web_buckets, private_buckets = _configured_storage_buckets() + if ucfg.get('object-storage.route.enabled'): + web_buckets.add(ucfg.get('object-storage.route.bucket')) + if ucfg.get('object-storage.data.enabled'): + private_buckets.add(ucfg.get('object-storage.data.bucket')) + if web_buckets & private_buckets: + raise ValueError("Public and private S3 bucket names overlap") + # assign default quota set for the user is not available if not ucfg.exists('object-storage.quota'): ucfg.put('object-storage.quota',cfg.get('seaweedfs.default-bucket-quota') or "1024") @@ -177,6 +213,7 @@ def create_ow_storage(state, ucfg: UserConfig, user_metadata: UserMetadata, owne logging.info(f"*** adding public bucket {bucket_name} for {namespace}") res = seaweedfsClient.make_bucket(bucket_name, ucfg.get('object-storage.quota')) res = seaweedfsClient.make_public_bucket(bucket_name) + res = res and seaweedfsClient.add_anonymous_access([bucket_name]) if(res): user_metadata.add_metadata("S3_BUCKET_STATIC",bucket_name) @@ -205,16 +242,22 @@ def create_ow_storage(state, ucfg: UserConfig, user_metadata: UserMetadata, owne def delete_ow_storage(ucfg): seaweedfsClient = SeaweedfsClient() namespace = ucfg.get("namespace") + data_bucket = ucfg.get('object-storage.data.bucket') if ucfg.get('object-storage.data.enabled') else None + route_bucket = ucfg.get('object-storage.route.bucket') if ucfg.get('object-storage.route.enabled') else None - if(ucfg.get('object-storage.data.enabled')): - bucket_name = ucfg.get('object-storage.data.bucket') - logging.info(f"*** removing private bucket {bucket_name} for {namespace}") - seaweedfsClient.force_bucket_remove(bucket_name) + # Revoke the public grant before deleting any bucket. A legacy + # configuration may use the same name for data and route storage. + if route_bucket: + logging.info(f"*** removing public bucket {route_bucket} for {namespace}") + if not seaweedfsClient.remove_anonymous_access(route_bucket): + raise RuntimeError(f"Could not revoke anonymous S3 access to bucket {route_bucket}; deletion stopped") - if(ucfg.get('object-storage.route.enabled')): - bucket_name = ucfg.get("object-storage.route.bucket") - logging.info(f"*** removing public bucket {bucket_name} for {namespace}") - seaweedfsClient.force_bucket_remove(bucket_name) + if data_bucket: + logging.info(f"*** removing private bucket {data_bucket} for {namespace}") + seaweedfsClient.force_bucket_remove(data_bucket) + + if route_bucket and route_bucket != data_bucket: + seaweedfsClient.force_bucket_remove(route_bucket) return seaweedfsClient.delete_user(namespace) @@ -257,7 +300,7 @@ def patch(status, action, owner=None): logging.info(msg) logging.info(f"*** hanlded request to {action} seaweedfs") except Exception as e: - logging.error('*** failed to update minio: %s' % e) + logging.error('*** failed to update seaweedfs: %s' % e) operator_util.patch_operator_status(status,'seaweedfs','error') def patch_ingresses(status, action, owner=None): @@ -266,7 +309,7 @@ def patch_ingresses(status, action, owner=None): """ try: logging.info(f"*** handling request to {action} seaweedfs ingresses") - data = util.get_minio_config_data() + data = util.get_seaweedfs_config_data() if action == 'update': msg = seaweedfs_ingress.create_seaweedfs_ingresses(data, owner) operator_util.patch_operator_status(status,'seaweedfs-ingresses','on') @@ -274,5 +317,5 @@ def patch_ingresses(status, action, owner=None): logging.info(msg) logging.info(f"*** hanlded request to {action} seaweedfs ingresses") except Exception as e: - logging.error('*** failed to update minio seaweedfs: %s' % e) + logging.error('*** failed to update seaweedfs ingresses: %s' % e) operator_util.patch_operator_status(status,'seaweedfs-ingresses','error') diff --git a/openserverless/seaweedfs_util.py b/openserverless/seaweedfs_util.py index 933b2f32..df91413f 100644 --- a/openserverless/seaweedfs_util.py +++ b/openserverless/seaweedfs_util.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. # -# this module wraps mc minio client using admin credentials +# this module wraps the minio S3 client SDK using admin credentials # to perform various operations import logging @@ -136,13 +136,19 @@ def add_user(self, username, access_key, secret_key,buckets,actions="Read,Write, f'-buckets={buckets} -actions={actions} -apply' ) return util.check(self._exec_weed_command(command),"add_user",True) - - def add_anonymous_access(self): - """ - adds a new seaweedfs user using the filer api - """ - command = 's3.configure -user=anonymous -actions=Read -apply' - return util.check(self._exec_weed_command(command),"add_user",True) + + def add_anonymous_access(self, buckets): + """Grant anonymous reads only to explicitly public web buckets.""" + if not buckets or any(not util.validate_bucket_name(bucket) for bucket in buckets): + raise ValueError("Invalid public bucket list") + command = f's3.configure -user=anonymous -actions=Read -buckets={",".join(sorted(set(buckets)))} -apply' + return util.check(self._exec_weed_command(command), "add_anonymous_web_access", True) + + def remove_anonymous_access(self, bucket): + if not util.validate_bucket_name(bucket): + raise ValueError("Invalid public bucket name") + command = f's3.configure -user=anonymous -actions=Read -buckets={bucket} -delete -apply' + return util.check(self._exec_weed_command(command), "remove_anonymous_web_access", True) def make_public_bucket(self, bucket_name): """ @@ -160,4 +166,4 @@ def delete_user(self, username): """ removes a user from seaweedfs """ - return util.check(self._exec_weed_command(f"s3.user.delete -name {username}"),"delete_user",True) + return util.check(self._exec_weed_command(f"s3.configure -user={username} -delete -apply"),"delete_user",True) diff --git a/openserverless/storage_static.py b/openserverless/storage_static.py index 7b4b02e8..541a7e76 100644 --- a/openserverless/storage_static.py +++ b/openserverless/storage_static.py @@ -249,7 +249,7 @@ def delete(owner=None): def patch(status, action, owner=None): """ - Called by the operator patcher to create/delete minio static component + Called by the operator patcher to create/delete the S3 static component """ try: logging.info(f"*** handling request to {action} static") diff --git a/openserverless/templates/00-minio-pvc.yaml b/openserverless/templates/00-minio-pvc.yaml deleted file mode 100644 index 1dcd0e85..00000000 --- a/openserverless/templates/00-minio-pvc.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: minio-pv-claim - namespace: openserverless - labels: - app: minio-storage-claim -spec: - accessModes: - - ReadWriteOnce - storageClassName: {{storage_class}} - resources: - requests: - storage: {{minio_volume_size}}Gi \ No newline at end of file diff --git a/openserverless/templates/01-minio-dep.yaml b/openserverless/templates/01-minio-dep.yaml deleted file mode 100644 index 401fca11..00000000 --- a/openserverless/templates/01-minio-dep.yaml +++ /dev/null @@ -1,69 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -apiVersion: apps/v1 # for k8s versions before 1.9.0 use apps/v1beta2 and before 1.8.0 use extensions/v1beta1 -kind: Deployment -metadata: - name: minio-deployment - namespace: openserverless -spec: - selector: - matchLabels: - app: minio - strategy: - type: Recreate - template: - metadata: - labels: - app: minio - spec: - volumes: - - name: data - persistentVolumeClaim: - claimName: minio-pv-claim - {% if applypodsecurity %} - securityContext: - fsGroup: 1001 - runAsUser: 1001 - {% endif %} - containers: - - name: minio - image: bitnamilegacy/minio:2025.6.13 - securityContext: - capabilities: - drop: - - ALL - seccompProfile: - type: RuntimeDefault - allowPrivilegeEscalation: false - readOnlyRootFilesystem: false - runAsNonRoot: true - command: ["/bin/bash", "-c", "minio server /data --console-address :9090"] - env: - - name: MINIO_ROOT_USER - value: {{minio_root_user}} - - name: MINIO_ROOT_PASSWORD - value: {{minio_root_password}} - ports: - - name: api - containerPort: 9000 - - name: content - containerPort: 9090 - volumeMounts: - - name: data # must match the volume name, above - mountPath: "/data" \ No newline at end of file diff --git a/openserverless/templates/02-minio-svc.yaml b/openserverless/templates/02-minio-svc.yaml deleted file mode 100644 index a7d5b1ba..00000000 --- a/openserverless/templates/02-minio-svc.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---- -apiVersion: v1 -kind: Service -metadata: - name: {{minio_host}} -spec: - type: NodePort - ports: - - port: 9000 - targetPort: 9000 - nodePort: 32090 - protocol: TCP - name: minio-api - - port: 9090 - targetPort: 9090 - nodePort: 32091 - protocol: TCP - name: minio-console - selector: - app: minio \ No newline at end of file diff --git a/openserverless/templates/alert-manager-02-cm.yaml b/openserverless/templates/alert-manager-02-cm.yaml index 3f694d42..dfa92add 100644 --- a/openserverless/templates/alert-manager-02-cm.yaml +++ b/openserverless/templates/alert-manager-02-cm.yaml @@ -34,6 +34,7 @@ data: {% endif %} receivers: + - name: discard {% if gmail %} - name: email-receiver email_configs: @@ -80,12 +81,13 @@ data: route: group_interval: 5m group_wait: 10s - {% if slack and slack_default %} - receiver: slack-notifications - {% endif %} {% if gmail and gmail_default %} receiver: email-receiver - {% endif %} + {% elif slack and slack_default %} + receiver: slack-notifications + {% else %} + receiver: discard + {% endif %} repeat_interval: 10m {% if slack or gmail %} diff --git a/openserverless/templates/kvrocks_manage_user_tpl.txt b/openserverless/templates/kvrocks_manage_user_tpl.txt index 7846d91f..77fc885e 100644 --- a/openserverless/templates/kvrocks_manage_user_tpl.txt +++ b/openserverless/templates/kvrocks_manage_user_tpl.txt @@ -1,3 +1,21 @@ +{#- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +-#} AUTH {{redis_password}} {% if mode == 'create'%} namespace add {{namespace}} {{password}} diff --git a/openserverless/templates/milvus-cfg-base.yaml b/openserverless/templates/milvus-cfg-base.yaml index 38f80354..ac1d7c6a 100644 --- a/openserverless/templates/milvus-cfg-base.yaml +++ b/openserverless/templates/milvus-cfg-base.yaml @@ -17,11 +17,12 @@ # --- apiVersion: v1 -kind: ConfigMap +kind: Secret metadata: name: openserverless-milvus namespace: openserverless -data: +type: Opaque +stringData: default.yaml: |+ # Copyright (C) 2019-2021 Zilliz. All rights reserved. # @@ -38,8 +39,8 @@ data: rootPath: milvus auth: enabled: true - userName: root - password: {{milvus_etcd_root_password}} + userName: {{milvus_etcd_username | tojson}} + password: {{milvus_etcd_password | tojson}} endpoints: {% for idx in etcd_range -%} - {{etcd_container}}-{{idx}}.openserverless-etcd-headless.openserverless.svc.cluster.local:2379 @@ -52,8 +53,8 @@ data: minio: address: {{bucket_server_hostname}} port: {{bucket_server_port}} - accessKeyID: {{milvus_s3_username}} - secretAccessKey: {{milvus_s3_password}} + accessKeyID: {{milvus_s3_username | tojson}} + secretAccessKey: {{milvus_s3_password | tojson}} useSSL: false bucketName: vectors rootPath: files @@ -140,4 +141,4 @@ data: common: security: authorizationEnabled: true - defaultRootPassword: {{milvus_root_password}} \ No newline at end of file + defaultRootPassword: {{milvus_root_password | tojson}} \ No newline at end of file diff --git a/openserverless/templates/milvus-cfg-slim-base.yaml b/openserverless/templates/milvus-cfg-slim-base.yaml index 0ede540e..d834180c 100644 --- a/openserverless/templates/milvus-cfg-slim-base.yaml +++ b/openserverless/templates/milvus-cfg-slim-base.yaml @@ -17,11 +17,12 @@ # --- apiVersion: v1 -kind: ConfigMap +kind: Secret metadata: name: openserverless-milvus namespace: openserverless -data: +type: Opaque +stringData: default.yaml: |+ # Copyright (C) 2019-2021 Zilliz. All rights reserved. # @@ -38,8 +39,8 @@ data: rootPath: milvus auth: enabled: true - userName: root - password: {{milvus_etcd_root_password}} + userName: {{milvus_etcd_username | tojson}} + password: {{milvus_etcd_password | tojson}} endpoints: {% for idx in etcd_range -%} - {{etcd_container}}-{{idx}}.openserverless-etcd-headless.openserverless.svc.cluster.local:2379 @@ -51,8 +52,8 @@ data: minio: address: {{bucket_server_hostname}} port: {{bucket_server_port}} - accessKeyID: {{milvus_s3_username}} - secretAccessKey: {{milvus_s3_password}} + accessKeyID: {{milvus_s3_username | tojson}} + secretAccessKey: {{milvus_s3_password | tojson}} useSSL: false bucketName: vectors rootPath: files @@ -129,4 +130,4 @@ data: common: security: authorizationEnabled: true - defaultRootPassword: {{milvus_root_password}} \ No newline at end of file + defaultRootPassword: {{milvus_root_password | tojson}} \ No newline at end of file diff --git a/openserverless/templates/mongodb-auth-openserverless.yaml b/openserverless/templates/mongodb-auth-openserverless.yaml deleted file mode 100644 index bd3ef58a..00000000 --- a/openserverless/templates/mongodb-auth-openserverless.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ---- -apiVersion: v1 -kind: Secret -metadata: - name: mongodb-openserverless-auth - namespace: openserverless -type: Opaque -stringData: - mongodb_openserverless_psw: {{mongo_openserverless_password}} diff --git a/openserverless/templates/mongodb-auth.yaml b/openserverless/templates/mongodb-auth.yaml deleted file mode 100644 index 8c04f3ac..00000000 --- a/openserverless/templates/mongodb-auth.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ---- -apiVersion: v1 -kind: Secret -metadata: - name: mongodb-admin-auth - namespace: openserverless -type: Opaque -stringData: - mongodb_admin_psw: {{mongo_admin_password}} diff --git a/openserverless/templates/mongodb-cm.yaml b/openserverless/templates/mongodb-cm.yaml deleted file mode 100644 index 5d3de463..00000000 --- a/openserverless/templates/mongodb-cm.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -apiVersion: v1 -kind: ConfigMap -metadata: - name: mongodb-cm - namespace: openserverless -data: - mongodb-js: |- - db = db.getSiblingDB('openserverless'); - - db.createUser({ - user: "{{mongo_openserverless_user}}", - pwd: "{{mongo_openserverless_password}}", - roles: [ - {role: "readWrite", db: "openserverless"} - ] - }); - - db.nuv_test_collection.insertOne({"message":"Welcome to openserverless!"}) \ No newline at end of file diff --git a/openserverless/templates/mongodb-config.yaml b/openserverless/templates/mongodb-config.yaml deleted file mode 100644 index a5947b93..00000000 --- a/openserverless/templates/mongodb-config.yaml +++ /dev/null @@ -1,77 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ---- -apiVersion: mongodbcommunity.mongodb.com/v1 -kind: MongoDBCommunity -metadata: - name: openserverless-mongodb - namespace: openserverless -spec: - members: 1 - type: ReplicaSet - version: "4.2.6" - security: - authentication: - modes: ["SCRAM"] - users: - - name: {{mongo_admin_user}} - db: admin - passwordSecretRef: # a reference to the secret that will be used to generate the user's password - name: mongodb-admin-auth - key: mongodb_admin_psw - roles: - - name: clusterAdmin - db: admin - - name: userAdminAnyDatabase - db: admin - scramCredentialsSecretName: mongodb-admin-scram - - name: {{mongo_openserverless_user}} - db: openserverless - passwordSecretRef: # a reference to the secret that will be used to generate the user's password - name: mongodb-openserverless-auth - key: mongodb_openserverless_psw - roles: - - name: readWrite - db: openserverless - scramCredentialsSecretName: mongodb-openserverless-scram - additionalMongodConfig: - storage.wiredTiger.engineConfig.journalCompressor: zlib - statefulSet: - spec: - template: - metadata: - labels: - name: mongodb - openserverless.mongodb.flavour: operator - annotations: - whisks.openserverless.org/annotate-version: "true" - spec: - containers: - - name: mongodb-agent - readinessProbe: - failureThreshold: 40 - initialDelaySeconds: 5 - timeout: 30 - volumeClaimTemplates: - - apiVersion: v1 - kind: PersistentVolumeClaim - metadata: - name: data-volume - spec: - resources: - requests: - storage: {{size}}G diff --git a/openserverless/templates/mongodb-sts.yaml b/openserverless/templates/mongodb-sts.yaml deleted file mode 100644 index 77375690..00000000 --- a/openserverless/templates/mongodb-sts.yaml +++ /dev/null @@ -1,90 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: openserverless-mongodb - labels: - app: mongodb - namespace: openserverless -spec: - serviceName: openserverless-mongodb-svc - replicas: 1 - selector: - matchLabels: - app: openserverless-mongodb - template: - metadata: - labels: - name: mongodb - app: openserverless-mongodb - openserverless.mongodb.flavour: standalone - annotations: - whisks.openserverless.org/annotate-version: "true" - spec: - containers: - - image: mongo:5.0.11 - name: mongodb - args: ["--dbpath","/data/db", "--bind_ip", "0.0.0.0"] - livenessProbe: - exec: - command: - - mongo - - --disableImplicitSessions - - --eval - - "db.adminCommand('ping')" - initialDelaySeconds: 40 - periodSeconds: 10 - timeoutSeconds: 10 - successThreshold: 1 - failureThreshold: 6 - readinessProbe: - exec: - command: - - mongo - - --disableImplicitSessions - - --eval - - "db.adminCommand('ping')" - initialDelaySeconds: 20 - periodSeconds: 10 - timeoutSeconds: 10 - successThreshold: 1 - failureThreshold: 6 - env: - - name: MONGO_INITDB_ROOT_USERNAME - value: {{mongo_admin_user}} - - name: MONGO_INITDB_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: mongodb-admin-auth - key: mongodb_admin_psw - volumeMounts: - - name: "mongodb-data-dir" - mountPath: "/data/db" - - name: "mongodb-init-dir" - mountPath: "/docker-entrypoint-initdb.d" - volumes: - - name: "mongodb-data-dir" - persistentVolumeClaim: - claimName: "mongodb-data" - - name: "mongodb-init-dir" - configMap: - name: mongodb-cm - items: - - key: mongodb-js - path: init-mongo.js \ No newline at end of file diff --git a/openserverless/templates/mongodb_manage_user_tpl.js b/openserverless/templates/mongodb_manage_user_tpl.js deleted file mode 100644 index 902a6c7b..00000000 --- a/openserverless/templates/mongodb_manage_user_tpl.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -conn = Mongo("mongodb://{{mongo_admin_user}}:{{mongo_admin_password}}@127.0.0.1:27017") -db = conn.getDB('admin') -db = db.getSiblingDB('{{database}}'); - -{% if mode == 'create'%} -db.createUser({ - user: "{{subject}}", - pwd: "{{auth}}", - roles: [ - {role: "readWrite", db: "{{subject}}"} - ] -}); -db.nuv_test_collection.insertOne({"message":"Welcome to openserverless!"}); -{% endif %} - -{% if mode == 'delete'%} -db.dropUser("{{subject}}", {w: "majority", wtimeout: 4000}); -db.dropDatabase(); -{% endif %} \ No newline at end of file diff --git a/openserverless/templates/nginx-static-sts.yaml b/openserverless/templates/nginx-static-sts.yaml index b7c13b8a..165c8249 100644 --- a/openserverless/templates/nginx-static-sts.yaml +++ b/openserverless/templates/nginx-static-sts.yaml @@ -34,7 +34,7 @@ spec: app: openserverless-static spec: initContainers: - - name: check-minio + - name: check-seaweedfs image: busybox:1.36.0 command: ['sh', '-c', "until nslookup {{storage_hostname}}; do echo waiting for storage; sleep 2; done"] containers: diff --git a/openserverless/templates/redis_manage_user_tpl.txt b/openserverless/templates/redis_manage_user_tpl.txt index 14d2d100..d3c55b40 100644 --- a/openserverless/templates/redis_manage_user_tpl.txt +++ b/openserverless/templates/redis_manage_user_tpl.txt @@ -1,33 +1,29 @@ +{#- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +-#} AUTH {{redis_password}} -{% if mode == 'create'%} +{% if mode == 'create' and redis_system_account|default(false) and namespace == 'openserverless' %} ACL SETUSER {{namespace}} ACL SETUSER {{namespace}} ON >{{password}} ACL SETUSER {{namespace}} +@all ~{{prefix}}* -{% endif %} - -{% if mode == 'delete'%} +{% elif mode == 'delete' %} ACL DELUSER {{namespace}} +{% elif mode in ['create', 'create_readonly', 'readonly', 'readandwrite'] %} +{# Tenant administration must use the shared Python ACL policy. Old callers fail closed. #} +ACL SETUSER {{namespace}} -@all resetkeys resetchannels clearselectors {% endif %} - -{% if mode == 'readonly'%} -ACL SETUSER {{namespace}} -@all ~{{prefix}}* -ACL SETUSER {{namespace}} +@read ~{{prefix}}* -ACL SETUSER {{namespace}} +info ~{{prefix}}* -ACL SETUSER {{namespace}} +del ~{{prefix}}* -{% endif %} - -{% if mode == 'create_readonly'%} -ACL SETUSER {{namespace}} -ACL SETUSER {{namespace}} ON >{{password}} -ACL SETUSER {{namespace}} -@all ~{{prefix}}* -ACL SETUSER {{namespace}} +@read ~{{prefix}}* -ACL SETUSER {{namespace}} +info ~{{prefix}}* -ACL SETUSER {{namespace}} +del ~{{prefix}}* -{% endif %} - -{% if mode == 'readandwrite'%} -ACL SETUSER {{namespace}} -@read ~{{prefix}}* -ACL SETUSER {{namespace}} -info ~{{prefix}}* -ACL SETUSER {{namespace}} -del ~{{prefix}}* -ACL SETUSER {{namespace}} +@all ~{{prefix}}* -{% endif %} \ No newline at end of file diff --git a/openserverless/user_handlers.py b/openserverless/user_handlers.py index 7b2422d6..9935dcf7 100644 --- a/openserverless/user_handlers.py +++ b/openserverless/user_handlers.py @@ -27,7 +27,6 @@ import openserverless.ferretdb as mdb import openserverless.kube as kube import openserverless.milvus_standalone as milvus -import openserverless.minio_deploy as minio_deploy import openserverless.postgres_operator as postgres import openserverless.redis as redis import openserverless.storage_static as static @@ -69,13 +68,10 @@ def whisk_user_create(spec, name, patch, **kwargs): logging.info(f"OpenWhisk api endpoints {ucfg.get('namespace')} added = {res}") state['api']= res - if(cfg.get('components.minio') and (ucfg.get('object-storage.data.enabled') or ucfg.get('object-storage.route.enabled'))): - minio_deploy.create_ow_storage(state, ucfg, user_metadata, owner) - if(cfg.get('components.seaweedfs') and (ucfg.get('object-storage.data.enabled') or ucfg.get('object-storage.route.enabled'))): seaweedfs.create_ow_storage(state, ucfg, user_metadata, owner) - if((cfg.get('components.minio') or cfg.get('components.seaweedfs')) and ucfg.get('object-storage.route.enabled') and cfg.get('components.static')): + if(cfg.get('components.seaweedfs') and ucfg.get('object-storage.route.enabled') and cfg.get('components.static')): res = static.create_ow_static_endpoint(ucfg,user_metadata, owner) logging.info("OpenWhisk static endpoint for %s added = %s", ucfg.get('namespace'), res) state['static']= res @@ -126,15 +122,11 @@ def whisk_user_delete(spec, name, **kwargs): res = cdb.delete_ow_user(ucfg.get("namespace")) logging.info(f"OpenWhisk subject {ucfg.get('namespace')} removed = {res}") - if(cfg.get('components.minio') and (ucfg.get('object-storage.data.enabled') or ucfg.get('object-storage.route.enabled'))): - res = minio_deploy.delete_ow_storage(ucfg) - logging.info(f"OpenWhisk namespace {ucfg.get('namespace')} MINIO storage removed = {res}") - if(cfg.get('components.seaweedfs') and (ucfg.get('object-storage.data.enabled') or ucfg.get('object-storage.route.enabled'))): res = seaweedfs.delete_ow_storage(ucfg) - logging.info(f"OpenWhisk namespace {ucfg.get('namespace')} SEAWEEDFS storage removed = {res}") + logging.info(f"OpenWhisk namespace {ucfg.get('namespace')} SEAWEEDFS storage removed = {res}") - if((cfg.get('components.minio') or cfg.get('components.seaweedfs')) and ucfg.get('object-storage.route.enabled') and cfg.get('components.static')): + if(cfg.get('components.seaweedfs') and ucfg.get('object-storage.route.enabled') and cfg.get('components.static')): res = static.delete_ow_static_endpoint(ucfg) logging.info(f"OpenWhisk static endpoint for {ucfg.get('namespace')} removed = {res}") @@ -185,7 +177,7 @@ def whisk_user_resume(spec, name, namespace,annotations, **kwargs): logging.info(f"Redis setup for {ucfg.get('namespace')} resumed = {res}") state['redis']= res - if(cfg.get('components.minio') and ucfg.get('object-storage.route.enabled') and cfg.get('components.static')): + if(cfg.get('components.seaweedfs') and ucfg.get('object-storage.route.enabled') and cfg.get('components.static')): state['static']= True if(cfg.get('components.mongodb') and ucfg.get('mongodb.enabled')): diff --git a/openserverless/util.py b/openserverless/util.py index 0560f06f..356dd63d 100644 --- a/openserverless/util.py +++ b/openserverless/util.py @@ -92,8 +92,8 @@ def get_default_storage_provisioner(): def get_ingress_namespace(runtime): """ - Attempt to determine the namespace where the ingress-nginx-controller service has been deployed - checking the openserverless.ingresslb + Attempt to determine the namespace where the ingress-nginx-controller service has been deployed + checking the openserverless.ingresslb - When set to 'auto' it will attempt to calculate it according to the kubernetes runtime - When set to <> 'auto' it will return the configured value. The configured value should be in the form / >>> import openserverless.config as cfg @@ -122,8 +122,8 @@ def get_ingress_namespace(runtime): def get_ingress_service_name(runtime): """ - Attempt to determine the namespace where the ingress-nginx-controller service has been deployed - checking the openserverless.ingresslb + Attempt to determine the namespace where the ingress-nginx-controller service has been deployed + checking the openserverless.ingresslb - When set to 'auto' it will attempt to calculate it according to the kubernetes runtime - When set to <> 'auto' it will return the configured value. The configured value should be in the form / >>> import openserverless.config as cfg @@ -302,20 +302,6 @@ def wait_for_http(url: str, timeout: int = 60, up_statuses: List[Union[int, str] logging.error(f"Protocol error: {e}") time.sleep(1) -# return mongodb configuration parameter with default valued if not configured -def get_mongodb_config_data(): - data = { - 'mongo_admin_user': cfg.get('mongodb.admin.user') or "whisk_user", - 'mongo_admin_password': cfg.get('mongodb.admin.password') or "0therPa55", - 'mongo_openserverless_user': cfg.get('mongodb.openserverless.user') or "openserverless", - 'mongo_openserverless_password': cfg.get('mongodb.openserverless.password') or "s0meP@ass3", - 'size': cfg.get('mongodb.volume-size') or 10, - 'pvcName': 'mongodb-data', - 'storageClass':cfg.get("openserverless.storageclass"), - 'pvcAccessMode':'ReadWriteOnce' - } - return data - def parse_image(img): """ Parse a string representing a pod image in the form : and return @@ -524,27 +510,6 @@ def get_service(jsonpath,namespace="openserverless"): raise Exception(f"could not find any svc matching jsonpath={jsonpath}") -# return minio configuration parameters with default values if not configured -def get_minio_config_data(): - data = { - "applypodsecurity":get_enable_pod_security(), - "name":"minio-deployment", - "container":"minio", - "minio_host": cfg.get('minio.host') or 'openserverless-minio', - "minio_volume_size": cfg.get('minio.volume-size') or "5", - "minio_root_user": cfg.get('minio.admin.user') or "minio", - "minio_root_password": cfg.get('minio.admin.password') or "minio123", - "storage_class": cfg.get("openserverless.storageclass"), - "minio_nuv_user": cfg.get('minio.openserverless.user') or "openserverless", - "minio_nuv_password": cfg.get('minio.openserverless.password') or "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", - "minio_s3_ingress_enabled": cfg.get('minio.ingress.s3-enabled') or False, - "minio_console_ingress_enabled": cfg.get('minio.ingress.console-enabled') or False, - "minio_s3_ingress_hostname": cfg.get('minio.ingress.s3-hostname') or "auto", - "minio_console_ingress_hostname": cfg.get('minio.ingress.console-hostname') or "auto" - } - minio_affinity_tolerations_data(data) - return data - # return postgres configuration parameter with default valued if not configured def get_postgres_config_data(): data = { @@ -603,7 +568,7 @@ def get_value_from_config_map(namespace="openserverless", path='{.metadata.annot def get_enable_pod_security(): """ Return true if there is the need to enable pod security context - for some specific pod. This is a test based on some empiric assumption on runtime + for some specific pod. This is a test based on some empiric assumption on runtime basis and/or storage class. @TODO: find a better way to determine when this function should return true. """ @@ -631,11 +596,6 @@ def get_storage_static_config_data(): "applypodsecurity": get_enable_pod_security() } - if cfg.get('components.minio'): - minio_host=cfg.get('minio.host') or "openserverless-minio" - minio_port=cfg.get('minio.port') or "9000" - data['storage_url']=f"http://{minio_host}.openserverless.svc.cluster.local:{minio_port}" - if cfg.get('components.seaweedfs'): seaweedfs_api_host = cfg.get("seaweedfs.host") or "seaweedfs" seaweedfs_api_port = cfg.get("seaweedfs.port") or "9000" @@ -662,12 +622,7 @@ def redis_affinity_tolerations_data(data): common_affinity_tolerations_data(data) data["pod_anti_affinity_name"] = "redis" -# populate specific affinity data for minio -def minio_affinity_tolerations_data(data): - common_affinity_tolerations_data(data) - data["pod_anti_affinity_name"] = "minio" - -# populate specific affinity data for minio +# populate specific affinity data for storage static def storage_static_affinity_tolerations_data(data): common_affinity_tolerations_data(data) data["pod_anti_affinity_name"] = "openserverless-static" @@ -708,7 +663,7 @@ def registry_affinity_tolerations_data(data): # populate specific affinity data for seaweedfs def seaweedfs_affinity_tolerations_data(data): common_affinity_tolerations_data(data) - data["pod_anti_affinity_name"] = "seaweedfs" + data["pod_anti_affinity_name"] = "seaweedfs" # wait for a pod name using a label selector and eventually an optional jsonpath @nuv_retry() @@ -914,7 +869,7 @@ def get_milvus_config_data(): 'milvus_etcd_password': cfg.get('milvus.password.etcd') or "0therPa55", 'milvus_etcd_root_password':cfg.get("etcd.root.password") or "s0meP@ass3wd", 'milvus_etcd_prefix': 'milvus', - 'milvus_s3_username': 'miniomilvus', + 'milvus_s3_username': 'seaweedfsmilvus', 'milvus_s3_password': cfg.get('milvus.password.s3') or "s0meP@ass3", 'milvus_bucket_name': 'vectors', 'milvus_bucket_quota': cfg.get('milvus.volume-size.bucket') or 10240, @@ -934,14 +889,9 @@ def get_milvus_config_data(): 'milvus_max_database_num': cfg.get('milvus.root-coord.max-database-num') or 64, 'slim': cfg.get('openserverless.slim') or False, } - - if cfg.get('components.minio'): - data["bucket_server_hostname"]="openserverless-minio" - data["bucket_server_port"]="9000" - if cfg.get('components.seaweedfs'): - data["bucket_server_hostname"]="seaweedfs" - data["bucket_server_port"]="9000" + data["bucket_server_hostname"]="seaweedfs" + data["bucket_server_port"]="9000" data["etcd_range"]=range(data["etcd_replicas"]) milvus_standalone_affinity_tolerations_data(data) diff --git a/openserverless/whisk_actions_deployer.py b/openserverless/whisk_actions_deployer.py index e7b2dba8..84ed6ab3 100644 --- a/openserverless/whisk_actions_deployer.py +++ b/openserverless/whisk_actions_deployer.py @@ -65,13 +65,13 @@ def prepare_secrets_action(): return secrets def prepare_content_action(): - minio_host= cfg.get("minio.host") or "openserverless-minio" - minio_port= cfg.get("minio.port") or "9000" - minio_full_host = f"{minio_host}.openserverless.svc.cluster.local" + s3_host= cfg.get("seaweedfs.host") or "seaweedfs" + s3_port= cfg.get("seaweedfs.port") or "9000" + s3_full_host = f"{s3_host}.openserverless.svc.cluster.local" content_inputs=[] - content_inputs.append({"key":"minio_host", "value":minio_full_host}) - content_inputs.append({"key":"minio_port", "value":minio_port}) + content_inputs.append({"key":"s3_host", "value":s3_full_host}) + content_inputs.append({"key":"s3_port", "value":s3_port}) content = { "name":"content", @@ -106,16 +106,16 @@ def prepare_psql_action(): return psql -def prepare_minio_action(): - minio = { - "name":"minio", - "function":"minio.zip", +def prepare_s3_action(): + s3 = { + "name":"s3", + "function":"s3.zip", "runtime":"python:sys", "web":"raw", "inputs":get_couchdb_inputs() } - return minio + return s3 def prepare_dev_upload_action(): dev_upload = { @@ -160,7 +160,7 @@ def prepare_system_actions(): actions.append(prepare_content_action()) actions.append(prepare_redis_action()) actions.append(prepare_psql_action()) - actions.append(prepare_minio_action()) + actions.append(prepare_s3_action()) actions.append(prepare_dev_upload_action()) actions.append(prepare_ferretdb_action()) actions.append(prepare_dev_download_action()) diff --git a/openserverless/zookeeper.py b/openserverless/zookeeper.py index 597ac018..5b3700ce 100644 --- a/openserverless/zookeeper.py +++ b/openserverless/zookeeper.py @@ -21,6 +21,7 @@ import openserverless.config as cfg import openserverless.enterprise_util as cfg_util import openserverless.util as util +import openserverless.messaging_security as security def get_zookeeper_pod_name(): pod_name = kube.kubectl("get", "pods", jsonpath="{.items[?(@.metadata.labels.app == 'zookeeper')].metadata.name}") @@ -56,7 +57,7 @@ def create(owner=None): cfg.put("state.zookeeper.spec", spec) - res = kube.apply(spec) + res = security.apply(spec, 'zookeeper') # dynamically detect zookeeper pod and wait for readiness util.wait_for_pod_ready("{.items[?(@.metadata.labels.app == 'zookeeper')].metadata.name}") @@ -70,7 +71,7 @@ def delete(): spec = cfg.get("state.zookeeper.spec") res = False if spec: - res = kube.delete(spec) + res = security.delete(spec, 'zookeeper') logging.info(f"delete zookeeper: {res}") return res diff --git a/poetry.lock b/poetry.lock index e18c50a4..ccde5c36 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# # This file is automatically @generated by Poetry 2.5.1 and should not be changed by hand. [[package]] diff --git a/run.sh b/run.sh index aed48f9d..5b9ef9ea 100755 --- a/run.sh +++ b/run.sh @@ -29,7 +29,7 @@ fi KOPF="${VIRTUAL_ENV:-/home/openserverless/.venv}/bin/kopf" if kubectl -n openserverless get cm/config then if test -x "$KOPF" - then exec "$KOPF" run -n openserverless -m openserverless openserverless/main.py openserverless/user_handlers.py openserverless/workflows.py "$@" + then exec "$KOPF" run -n openserverless -m openserverless.main -m openserverless.user_handlers -m openserverless.workflows "$@" else echo "kopf not found at $KOPF - run 'task setup' first." fi else echo "You need to 'kubectl apply -f deploy/permissions' before starting the operator." diff --git a/tests/aks/whisk.yaml b/tests/aks/whisk.yaml index 839e3bc9..0ece4c21 100644 --- a/tests/aks/whisk.yaml +++ b/tests/aks/whisk.yaml @@ -41,9 +41,9 @@ spec: cron: true # tls enabled or not tls: false - # minio enabled or not - minio: true - # minio static enabled or not + # seaweedfs enabled or not + seaweedfs: true + # seaweedfs static enabled or not static: true # postgres enabled or not postgres: true @@ -91,21 +91,9 @@ spec: prefix: nuv password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 2 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/eks/whisk-full.yaml b/tests/eks/whisk-full.yaml index cb755e3d..b1f89011 100644 --- a/tests/eks/whisk-full.yaml +++ b/tests/eks/whisk-full.yaml @@ -41,11 +41,11 @@ spec: cron: true # enable TLS tls: true - # minio enabled or not - minio: true + # seaweedfs enabled or not + seaweedfs: true # zookeeper enabled or not zookeeper: true - # minio static enabled or not + # seaweedfs static enabled or not static: true monitoring: true # postgres enabled or not @@ -123,21 +123,9 @@ spec: prefix: nuv password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 10 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/eks/whisk.yaml b/tests/eks/whisk.yaml index 1293754a..85cbbc08 100644 --- a/tests/eks/whisk.yaml +++ b/tests/eks/whisk.yaml @@ -41,9 +41,9 @@ spec: cron: true # tls enabled or not tls: true - # minio enabled or not - minio: true - # minio static enabled or not + # seaweedfs enabled or not + seaweedfs: true + # seaweedfs static enabled or not static: true # postgres enabled or not postgres: true @@ -91,21 +91,9 @@ spec: prefix: nuv password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 2 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/generic/whisk-full.yaml b/tests/generic/whisk-full.yaml index 966145c8..2d08375b 100644 --- a/tests/generic/whisk-full.yaml +++ b/tests/generic/whisk-full.yaml @@ -43,11 +43,11 @@ spec: cron: true # enable TLS tls: true - # minio enabled or not - minio: true + # seaweedfs enabled or not + seaweedfs: true # zookeeper enabled or not zookeeper: true - # minio static enabled or not + # seaweedfs static enabled or not static: true # postgres enabled or not postgres: true @@ -107,21 +107,9 @@ spec: prefix: nuv password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 10 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/generic/whisk.yaml b/tests/generic/whisk.yaml index 78c5a093..823c009c 100644 --- a/tests/generic/whisk.yaml +++ b/tests/generic/whisk.yaml @@ -42,9 +42,9 @@ spec: cron: false # tls enabled or not tls: true - # minio enabled or not - minio: false - # minio static enabled or not + # seaweedfs enabled or not + seaweedfs: false + # seaweedfs static enabled or not static: false # postgres enabled or not postgres: false @@ -91,21 +91,9 @@ spec: prefix: nuv password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 2 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/gke/whisk.yaml b/tests/gke/whisk.yaml index 520c826f..0f68312b 100644 --- a/tests/gke/whisk.yaml +++ b/tests/gke/whisk.yaml @@ -41,9 +41,9 @@ spec: cron: true # tls enabled or not tls: false - # minio enabled or not - minio: true - # minio static enabled or not + # seaweedfs enabled or not + seaweedfs: true + # seaweedfs static enabled or not static: true # postgres enabled or not postgres: true @@ -91,21 +91,9 @@ spec: prefix: nuv password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 2 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/k3s/minimal.yaml b/tests/k3s/minimal.yaml index d656af2c..7308435d 100644 --- a/tests/k3s/minimal.yaml +++ b/tests/k3s/minimal.yaml @@ -41,8 +41,8 @@ spec: cron: false # enable TLS tls: true - # minio enabled or not - minio: false + # seaweedfs enabled or not + seaweedfs: false openwhisk: namespaces: whisk-system: 789c46b1-71f6-4ed5-8c54-816aa4f8c502:abcfO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP @@ -59,14 +59,7 @@ spec: user: controller_admin password: s0meP@ass2 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 kafka: host: kafka volume-size: 10 @@ -75,11 +68,11 @@ spec: tls: acme-registered-email: francesco@nuvolaris.io acme-server-url: https://acme-v02.api.letsencrypt.org/directory - minio: + seaweedfs: volume-size: 2 openserverless: - root-user: minioadmin - root-password: minioadmin + user: openserverless + password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG configs: limits: actions: diff --git a/tests/k3s/whisk-full.yaml b/tests/k3s/whisk-full.yaml index 63804a5d..ef1ea8cd 100644 --- a/tests/k3s/whisk-full.yaml +++ b/tests/k3s/whisk-full.yaml @@ -41,11 +41,11 @@ spec: cron: false # enable TLS tls: false - # minio enabled or not - minio: true + # seaweedfs enabled or not + seaweedfs: true # zookeeper enabled or not zookeeper: false - # minio static enabled or not + # seaweedfs static enabled or not static: true # monitoring enabled or not monitoring: false @@ -126,21 +126,9 @@ spec: prefix: nuv password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 10 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/k3s/whisk.yaml b/tests/k3s/whisk.yaml index 95885838..b89bb434 100644 --- a/tests/k3s/whisk.yaml +++ b/tests/k3s/whisk.yaml @@ -41,11 +41,11 @@ spec: cron: false # enable TLS tls: false - # minio enabled or not - minio: true + # seaweedfs enabled or not + seaweedfs: true # zookeeper enabled or not zookeeper: false - # minio static enabled or not + # seaweedfs static enabled or not static: true # monitoring enabled or not monitoring: false @@ -130,26 +130,14 @@ spec: prefix: nuv password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: ingress: s3-enabled: true s3-hostname: auto console-enabled: false console-hostname: auto volume-size: 10 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/kind/dbonly.yaml b/tests/kind/dbonly.yaml index 2ed0c780..a31ab7d1 100644 --- a/tests/kind/dbonly.yaml +++ b/tests/kind/dbonly.yaml @@ -34,8 +34,6 @@ spec: mongodb: false # start redis redis: false - # start s3ninja - s3bucket: false openwhisk: namespaces: whisk-system: 789c46b1-71f6-4ed5-8c54-816aa4f8c502:abczO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP @@ -54,11 +52,7 @@ spec: user: controller_admin password: s0meP@ass2 mongodb: - host: mongodb volume-size: 10 - admin: - user: admin - password: 0therPa55 kafka: host: kafka volume-size: 10 diff --git a/tests/kind/ferretdb_test.ipy b/tests/kind/ferretdb_test.ipy index de9ceaf5..6b34d4a7 100644 --- a/tests/kind/ferretdb_test.ipy +++ b/tests/kind/ferretdb_test.ipy @@ -15,27 +15,57 @@ # specific language governing permissions and limitations # under the License. # -#!kubectl -n openserverless delete all --all -#!kubectl -n openserverless delete pvc --all -# -#import openserverless.config as cfg -#import openserverless.ferretdb as mdb -#import openserverless.kube as kube -#import openserverless.postgres_operator as postgres -#import openserverless.testutil as tu -# -## test -#assert(cfg.configure(tu.load_sample_config())) -#cfg.detect_storage() -#assert(postgres.create()) -#assert(mdb.create()) -# -#while not kube.wait("pod/openserverless-mongodb-0", "condition=ready"): pass -# -#assert(kube.get("service/openserverless-mongodb-svc")) -#assert(kube.kubectl("get", f"cm/config", jsonpath="{.metadata.annotations.mongodb_url}")) -#assert(mdb.delete()) -#assert(postgres.delete()) -# -#!kubectl -n openserverless delete all --all -#!kubectl -n openserverless delete pvc --all \ No newline at end of file +!kubectl -n openserverless delete all --all +!kubectl -n openserverless delete pvc --all + +import openserverless.config as cfg +import openserverless.ferretdb as mdb +import openserverless.kube as kube +import openserverless.util as util +import openserverless.postgres_operator as postgres +import openserverless.testutil as tu +from openserverless.user_config import UserConfig +from openserverless.user_metadata import UserMetadata + +# test +assert(cfg.configure(tu.load_sample_config())) +cfg.detect_storage() +assert(postgres.create()) +assert(mdb.create()) + +while not kube.wait("pod/openserverless-mongodb-0", "condition=ready"): pass + +assert(kube.get("service/openserverless-mongodb-svc")) +assert(kube.kubectl("get", f"cm/config", jsonpath="{.metadata.annotations.mongodb_url}")) + +# user provisioning: ferretdb backs the mongodb.* user configuration on postgres +spec = tu.load_sample_config("whisk-user") +assert(spec) + +ucfg = UserConfig(spec) +udata = UserMetadata(ucfg) + +assert(mdb.create_db_user(ucfg, udata)) +assert(any(e['key'] == "MONGODB_URL" for e in udata.get_metadata()['metadata'])) + +# Prove provisioning created the intended database and login grants, rather than merely +# returning psql output. Keep credentials inside the database pod environment. +primary = util.get_pod_name_by_selector("app=openserverless-postgres", "{.items[?(@.metadata.labels.replicationRole == 'primary')].metadata.name}") +assert(primary) + +def scalar(query): + return kube.kubectl("exec", primary, "--", "sh", "-c", + 'PGPASSWORD="$POSTGRES_PASSWORD" exec psql --no-psqlrc --username=postgres --dbname=postgres --tuples-only --no-align --set=ON_ERROR_STOP=1 --command "$1"', + "ferretdb-probe", query).strip() + +database = (ucfg.get('mongodb.database') + '_ferretdb').replace("'", "''") +username = (ucfg.get('namespace') + '_ferretdb').replace("'", "''") +assert(scalar(f"SELECT count(*) FROM pg_database d, pg_roles r WHERE d.datname='{database}' AND r.rolname='{username}' AND r.rolcanlogin AND has_database_privilege(r.oid,d.oid,'CONNECT') AND has_database_privilege(r.oid,d.oid,'CREATE')") == '1') +assert(mdb.delete_db_user(ucfg.get('namespace'),ucfg.get('mongodb.database'))) +assert(scalar(f"SELECT ((EXISTS(SELECT 1 FROM pg_database WHERE datname='{database}')) OR (EXISTS(SELECT 1 FROM pg_roles WHERE rolname='{username}')))::int") == '0') + +assert(mdb.delete()) +assert(postgres.delete()) + +!kubectl -n openserverless delete all --all +!kubectl -n openserverless delete pvc --all diff --git a/tests/kind/milvus_standalone_slim_test.ipy b/tests/kind/milvus_standalone_slim_test.ipy index 7c02d48a..cf0c2976 100644 --- a/tests/kind/milvus_standalone_slim_test.ipy +++ b/tests/kind/milvus_standalone_slim_test.ipy @@ -18,10 +18,13 @@ # import os +import atexit +import contextlib +from unittest.mock import patch +from openserverless.datastore_connection import service_endpoint import openserverless.config as cfg import openserverless.milvus_standalone as milvus -import openserverless.minio_deploy as minio import openserverless.seaweedfs_deploy as seaweedfs import openserverless.etcd as etcd import openserverless.testutil as tu @@ -42,28 +45,34 @@ assert(cfg.detect_storage()["openserverless.storageclass"]) assert(cfg.get("openserverless.slim") == True) assert(cfg.get("components.milvus") == True) -is_minio = cfg.get('components.minio') or False is_seaweedfs = cfg.get('components.seaweedfs') or False -assert(is_minio or is_seaweedfs) +assert(is_seaweedfs) -# for this test minioClient and Milvus should see this env variable -if is_minio: - os.environ['MINIO_API_HOST']='localhost' -if is_seaweedfs: - os.environ['SEAWEEDFS_API_HOST']='localhost' - os.environ['SEAWEEDFS_API_PORT']='9090' +# for this test seaweedfs and Milvus should see this env variable +os.environ['SEAWEEDFS_API_HOST']='localhost' +os.environ['SEAWEEDFS_API_PORT']='9090' os.environ['MILVUS_API_HOST']='localhost' assert(etcd.create()) -if is_minio: - assert(minio.create()) -if is_seaweedfs: - assert(seaweedfs.create()) +assert(seaweedfs.create()) -assert(milvus.create()) +endpoint_stack = contextlib.ExitStack() +atexit.register(endpoint_stack.close) +finish_reconcile = milvus.datastore_security.finish_milvus_reconcile +local_endpoint = None + +def finish_and_connect(): + global local_endpoint + finish_reconcile() + local_endpoint = endpoint_stack.enter_context(service_endpoint('openserverless-milvus', 19530)) + os.environ['MILVUS_API_HOST'] = local_endpoint[0] + os.environ['MILVUS_API_PORT'] = str(local_endpoint[1]) + +with patch.object(milvus.datastore_security, 'finish_milvus_reconcile', side_effect=finish_and_connect): + assert(milvus.create()) # generate some user config ucfg = UserConfig({ @@ -101,7 +110,7 @@ assert port # test client -client = MilvusClient(uri=f'http://localhost:{port}',token=token, db_name=database) +client = MilvusClient(uri=f'http://{local_endpoint[0]}:{local_endpoint[1]}',token=token, db_name=database) client.create_collection("testcollection", dimension=128) collections = client.list_collections() or [] assert("testcollection" in collections) @@ -112,10 +121,8 @@ assert(milvus.delete_ow_milvus(ucfg)) assert(milvus.delete()) assert(etcd.delete()) -if is_minio: - assert(minio.delete()) -if is_seaweedfs: - assert(seaweedfs.delete()) +assert(seaweedfs.delete()) +endpoint_stack.close() # final cleanup tu.run_proc("kubectl -n openserverless delete all --all") diff --git a/tests/kind/milvus_standalone_test.ipy b/tests/kind/milvus_standalone_test.ipy index 26acc2ea..2931c0f1 100644 --- a/tests/kind/milvus_standalone_test.ipy +++ b/tests/kind/milvus_standalone_test.ipy @@ -18,11 +18,14 @@ # import os +import atexit +import contextlib +from unittest.mock import patch +from openserverless.datastore_connection import service_endpoint import openserverless.config as cfg import openserverless.etcd as etcd import openserverless.milvus_standalone as milvus -import openserverless.minio_deploy as minio import openserverless.seaweedfs_deploy as seaweedfs import openserverless.testutil as tu from openserverless.milvus_simple_client import MilvusSimpleClient as MilvusClient @@ -42,26 +45,31 @@ assert(cfg.detect_storage()["openserverless.storageclass"]) assert(cfg.get("openserverless.slim") == None or cfg.get("openserverless.slim") == False) assert(cfg.get("components.milvus") == True) -is_minio = cfg.get('components.minio') or False is_seaweedfs = cfg.get('components.seaweedfs') or False -assert(is_minio or is_seaweedfs) +assert(is_seaweedfs) -# for this test minioClient and Milvus should see this env variable -# for this test minioClient and Milvus should see this env variable -if is_minio: - os.environ['MINIO_API_HOST']='localhost' -if is_seaweedfs: - os.environ['SEAWEEDFS_API_HOST']='localhost' - os.environ['SEAWEEDFS_API_PORT']='9090' +# for this test seaweedfs and Milvus should see this env variable +os.environ['SEAWEEDFS_API_HOST']='localhost' +os.environ['SEAWEEDFS_API_PORT']='9090' os.environ['MILVUS_API_HOST']='localhost' assert(etcd.create()) -if is_minio: - assert(minio.create()) -if is_seaweedfs: - assert(seaweedfs.create()) -assert(milvus.create()) +assert(seaweedfs.create()) +endpoint_stack = contextlib.ExitStack() +atexit.register(endpoint_stack.close) +finish_reconcile = milvus.datastore_security.finish_milvus_reconcile +local_endpoint = None + +def finish_and_connect(): + global local_endpoint + finish_reconcile() + local_endpoint = endpoint_stack.enter_context(service_endpoint('openserverless-milvus', 19530)) + os.environ['MILVUS_API_HOST'] = local_endpoint[0] + os.environ['MILVUS_API_PORT'] = str(local_endpoint[1]) + +with patch.object(milvus.datastore_security, 'finish_milvus_reconcile', side_effect=finish_and_connect): + assert(milvus.create()) # generate some user config ucfg = UserConfig({ @@ -98,7 +106,7 @@ assert database assert port # test client -client = MilvusClient(uri=f'http://localhost:{port}',token=token, db_name=database) +client = MilvusClient(uri=f'http://{local_endpoint[0]}:{local_endpoint[1]}',token=token, db_name=database) client.create_collection("testcollection", dimension=128) collections = client.list_collections() or [] assert("testcollection" in collections) @@ -110,10 +118,8 @@ assert(milvus.delete_ow_milvus(ucfg)) assert(milvus.delete()) assert(etcd.delete()) -if is_minio: - assert(minio.delete()) -if is_seaweedfs: - assert(seaweedfs.delete()) +assert(seaweedfs.delete()) +endpoint_stack.close() tu.run_proc("kubectl -n openserverless delete all --all") tu.run_proc("kubectl -n openserverless delete pvc --all") diff --git a/tests/kind/minimal.yaml b/tests/kind/minimal.yaml index 71bdc31e..284ea31e 100644 --- a/tests/kind/minimal.yaml +++ b/tests/kind/minimal.yaml @@ -36,15 +36,13 @@ spec: mongodb: false # start redis redis: true - # start s3ninja - s3bucket: false # start cron based action parser cron: false # tls enabled or not tls: false - # minio enabled or not - minio: false - # minio static enabled or not + # seaweedfs enabled or not + seaweedfs: false + # seaweedfs static enabled or not static: false openwhisk: namespaces: @@ -64,16 +62,7 @@ spec: user: controller_admin password: s0meP@ass2 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False kafka: host: kafka volume-size: 10 @@ -87,11 +76,11 @@ spec: tls: acme-registered-email: francesco@nuvolaris.io acme-server-url: https://acme-staging-v02.api.letsencrypt.org/directory - minio: + seaweedfs: volume-size: 2 openserverless: - root-user: minioadmin - root-password: minioadmin + user: openserverless + password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG configs: limits: actions: diff --git a/tests/kind/minio_quota_test.ipy b/tests/kind/minio_quota_test.ipy deleted file mode 100644 index dcb96360..00000000 --- a/tests/kind/minio_quota_test.ipy +++ /dev/null @@ -1,69 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -import os - -import openserverless.config as cfg -import openserverless.minio_deploy as minio -import openserverless.minio_util as mutil -import openserverless.testutil as tu -import openserverless.util as util -import json - -tu.run_proc("kubectl -n openserverless delete all --all") -tu.run_proc("kubectl -n openserverless delete pvc --all") - -# test -assert(cfg.configure(tu.load_sample_config())) -assert(cfg.detect_storage()["openserverless.storageclass"]) - -os.environ['MINIO_API_HOST']='localhost' -assert(minio.create()) - -pod_name = util.get_pod_name("{.items[?(@.metadata.labels.app == 'minio')].metadata.name}") -assert(pod_name) - -minioClient = mutil.MinioClient() -assert(minioClient.make_bucket("ftt-data")) -assert(minioClient.make_public_bucket("ftt-web")) -assert(minioClient.add_user("ftt","jgfkjsgcasgfjgdsafgsdkfgkaj")) -assert(minioClient.assign_rw_bucket_policy_to_user("ftt",["ftt-web/*","ftt-data/*"])) - -assert(minioClient.assign_quota_to_bucket("ftt-data",10)) - -# upload 7M -tu.run_proc(f"dd if=/dev/urandom of=/tmp/__minio_test_file1 bs=1M count=7") -minioClient.mc("cp", f"/tmp/__minio_test_file1", f"local_ftt/ftt-data/file1") -os.remove(f"/tmp/__minio_test_file1") - -# check quota is 7M -assert(minioClient.mc("du", "local/ftt-data", "--json")) -last_output = minioClient.get_last_output() -quota_info = json.loads(last_output) -size = quota_info.get('size', 0) -assert(size == 7340032) - - -tu.run_proc(f"dd if=/dev/urandom of=/tmp/__minio_test_file2 bs=1M count=15") -res = minioClient.mc("cp", f"/tmp/__minio_test_file2", f"local_ftt/ftt-data/file2") -assert (res is False) -last_output = minioClient.get_last_output() -os.remove(f"/tmp/__minio_test_file2") -assert(last_output.index("Bucket quota exceeded") >= 0) - - -assert(minio.delete()) \ No newline at end of file diff --git a/tests/kind/minio_test.ipy b/tests/kind/minio_test.ipy deleted file mode 100644 index 0764bf82..00000000 --- a/tests/kind/minio_test.ipy +++ /dev/null @@ -1,47 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -import os - -import openserverless.config as cfg -import openserverless.kube as kube -import openserverless.minio_deploy as minio -import openserverless.testutil as tu -import openserverless.util as util - -tu.run_proc("kubectl -n openserverless delete all --all") -tu.run_proc("kubectl -n openserverless delete pvc --all") - -# test -assert(cfg.configure(tu.load_sample_config())) -assert(cfg.detect_storage()["openserverless.storageclass"]) - -# for this test minioClient should see this env variable -os.environ['MINIO_API_HOST']='localhost' - -assert(minio.create()) - -pod_name = util.get_pod_name("{.items[?(@.metadata.labels.app == 'minio')].metadata.name}") -assert(pod_name) - -actual_minio_root_user = kube.kubectl("get", f"pods/{pod_name}", jsonpath="{.spec.containers[0].env[0].value}") -configured_minio_root_user = cfg.get('minio.admin.user') -assert(actual_minio_root_user[0] == configured_minio_root_user) - -cm_minio_url = kube.kubectl("get", f"cm/config", jsonpath="{.metadata.annotations.s3_host}") -assert(cm_minio_url) -assert(minio.delete()) \ No newline at end of file diff --git a/tests/kind/mongodb_op_test.ipy b/tests/kind/mongodb_op_test.ipy deleted file mode 100644 index 84ec513c..00000000 --- a/tests/kind/mongodb_op_test.ipy +++ /dev/null @@ -1,41 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -!kubectl -n openserverless delete all --all -!kubectl -n openserverless delete pvc --all - -import openserverless.config as cfg -import openserverless.kube as kube -import openserverless.mongodb as mdb -import openserverless.testutil as tu - -# test -assert(cfg.configure(tu.load_sample_config())) -cfg.detect_storage() -assert(cfg.put("mongodb.useOperator", True)) -assert(mdb.create()) -while not kube.wait("pod/openserverless-mongodb-0", "condition=ready"): pass -assert(kube.get("service/openserverless-mongodb-svc")) -assert(kube.get("mdbc/openserverless-mongodb")) - -!kubectl get secret -n openserverless -ojson openserverless-mongodb-openserverless-openserverless | jq -r '.data | with_entries(.value |= @base64d)' - -assert(kube.kubectl("get", f"cm/config", jsonpath="{.metadata.annotations.mongodb_url}")) -assert(mdb.delete()) - -#!kubectl -n openserverless delete all --all -#!kubectl -n openserverless delete pvc --all \ No newline at end of file diff --git a/tests/kind/mongodb_std_test.ipy b/tests/kind/mongodb_std_test.ipy deleted file mode 100644 index 0cf7261f..00000000 --- a/tests/kind/mongodb_std_test.ipy +++ /dev/null @@ -1,42 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -!kubectl -n openserverless delete all --all -!kubectl -n openserverless delete pvc --all - -import openserverless.config as cfg -import openserverless.kube as kube -import openserverless.mongodb as mdb -import openserverless.testutil as tu - -# test -assert(cfg.configure(tu.load_sample_config())) -cfg.detect_storage() -assert(cfg.put("mongodb.useOperator", False)) -assert(mdb.create()) -while not kube.wait("pod/openserverless-mongodb-0", "condition=ready"): pass -assert(kube.get("service/openserverless-mongodb-svc")) - -flavour = kube.kubectl("get", "pod/openserverless-mongodb-0", jsonpath=r"{.metadata.labels.openserverless\.mongodb\.flavour}") -assert("standalone"==flavour[0]) - -assert(kube.kubectl("get", f"cm/config", jsonpath="{.metadata.annotations.mongodb_url}")) - -assert(mdb.delete()) - -!kubectl -n openserverless delete all --all -!kubectl -n openserverless delete pvc --all \ No newline at end of file diff --git a/tests/kind/mongodb_user_test.ipy b/tests/kind/mongodb_user_test.ipy deleted file mode 100644 index 235877ba..00000000 --- a/tests/kind/mongodb_user_test.ipy +++ /dev/null @@ -1,49 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -!kubectl -n openserverless delete all --all -!kubectl -n openserverless delete pvc --all - -import openserverless.config as cfg -import openserverless.kube as kube -import openserverless.mongodb as mdb -import openserverless.testutil as tu -from openserverless.user_config import UserConfig -from openserverless.user_metadata import UserMetadata - -# test -assert(cfg.configure(tu.load_sample_config())) -assert(cfg.put("mongodb.useOperator", False)) -assert(mdb.create()) -while not kube.wait("pod/openserverless-mongodb-0", "condition=ready"): pass -assert(kube.get("service/openserverless-mongodb-svc")) - -flavour = kube.kubectl("get", "pod/openserverless-mongodb-0", jsonpath=r"{.metadata.labels.openserverless\.mongodb\.flavour}") -assert("standalone"==flavour[0]) - -spec = tu.load_sample_config("whisk-user") -assert(spec) - -ucfg = UserConfig(spec) -udata = UserMetadata(ucfg) - -assert(mdb.create_db_user(ucfg, udata)) -assert(mdb.delete_db_user(ucfg.get('namespace'),ucfg.get('mongodb.database'))) -assert(mdb.delete()) - -!kubectl -n openserverless delete all --all -!kubectl -n openserverless delete pvc --all \ No newline at end of file diff --git a/tests/kind/registry_test.ipy b/tests/kind/registry_test.ipy index ce37b9a2..1289a723 100644 --- a/tests/kind/registry_test.ipy +++ b/tests/kind/registry_test.ipy @@ -33,7 +33,6 @@ tu.run_proc("kubectl -n openserverless delete pvc --all") assert(cfg.configure(tu.load_sample_config())) assert(cfg.detect_storage()["openserverless.storageclass"]) -# for this test minioClient should see this env variable assert(registry_deploy.create()) pod_name = util.get_pod_name("{.items[?(@.metadata.labels.app == 'registry')].metadata.name}") diff --git a/tests/kind/seaweedfs_test.ipy b/tests/kind/seaweedfs_test.ipy index f43a0ffd..55f0c28c 100644 --- a/tests/kind/seaweedfs_test.ipy +++ b/tests/kind/seaweedfs_test.ipy @@ -37,7 +37,7 @@ os.environ['SEAWEEDFS_API_PORT']='9090' assert(cfg.configure(tu.load_sample_config())) assert(cfg.detect_storage()["openserverless.storageclass"]) -# for this test minioClient should see this env variable +# for this test the seaweedfs filer should see this env variable assert(seaweedfs_deploy.create()) pod_name = util.get_pod_name("{.items[?(@.metadata.labels.app == 'seaweedfs')].metadata.name}") diff --git a/tests/kind/minio_util_test.ipy b/tests/kind/seaweedfs_util_test.ipy similarity index 66% rename from tests/kind/minio_util_test.ipy rename to tests/kind/seaweedfs_util_test.ipy index 16be1a21..b56dd7ae 100644 --- a/tests/kind/minio_util_test.ipy +++ b/tests/kind/seaweedfs_util_test.ipy @@ -18,8 +18,8 @@ import os import openserverless.config as cfg -import openserverless.minio_deploy as minio -import openserverless.minio_util as mutil +import openserverless.seaweedfs_deploy as seaweedfs +import openserverless.seaweedfs_util as sus import openserverless.testutil as tu import openserverless.util as util @@ -32,16 +32,18 @@ tu.run_proc("kubectl -n openserverless delete pvc --all") assert(cfg.configure(tu.load_sample_config())) assert(cfg.detect_storage()["openserverless.storageclass"]) -os.environ['MINIO_API_HOST']='localhost' -assert(minio.create()) +# for this test the seaweedfsClient should see these env variables +os.environ['SEAWEEDFS_API_HOST']='localhost' +os.environ['SEAWEEDFS_API_PORT']='9090' +assert(seaweedfs.create()) -pod_name = util.get_pod_name("{.items[?(@.metadata.labels.app == 'minio')].metadata.name}") +pod_name = util.get_pod_name("{.items[?(@.metadata.labels.app == 'seaweedfs')].metadata.name}") assert(pod_name) -minioClient = mutil.MinioClient() -assert(minioClient.make_bucket("ftt-data")) -assert(minioClient.make_public_bucket("ftt-web")) -assert(minioClient.add_user("ftt","jgfkjsgcasgfjgdsafgsdkfgkaj")) -assert(minioClient.assign_rw_bucket_policy_to_user("ftt",["ftt-web/*","ftt-data/*"])) +seaweedfsClient = sus.SeaweedfsClient() +assert(seaweedfsClient.make_bucket("ftt-data")) +assert(seaweedfsClient.make_public_bucket("ftt-web")) +assert(seaweedfsClient.add_user("ftt","jgfkjsgcasgfjgdsafgsdkfgkaj","jgfkjsgcasgfjgdsafgsdkfgkaj","ftt-web,ftt-data")) +assert(seaweedfsClient.delete_user("ftt")) -assert(minio.delete()) \ No newline at end of file +assert(seaweedfs.delete()) \ No newline at end of file diff --git a/tests/kind/minio_static_test.ipy b/tests/kind/storage_static_test.ipy similarity index 82% rename from tests/kind/minio_static_test.ipy rename to tests/kind/storage_static_test.ipy index eda93738..7737da25 100644 --- a/tests/kind/minio_static_test.ipy +++ b/tests/kind/storage_static_test.ipy @@ -19,7 +19,7 @@ import os import openserverless.config as cfg -import openserverless.minio_deploy as minio +import openserverless.seaweedfs_deploy as seaweedfs import openserverless.storage_static as static import openserverless.testutil as tu @@ -31,11 +31,12 @@ tu.run_proc("kubectl -n openserverless delete pvc --all") assert(cfg.configure(tu.load_sample_config())) assert(cfg.detect_storage()["openserverless.storageclass"]) -# for this test minioClient should see this env variable -os.environ['MINIO_API_HOST']='localhost' +# for this test the seaweedfs filer should see these env variables +os.environ['SEAWEEDFS_API_HOST']='localhost' +os.environ['SEAWEEDFS_API_PORT']='9090' -assert(minio.create()) +assert(seaweedfs.create()) assert(static.create()) assert(static.delete()) -assert(minio.delete()) \ No newline at end of file +assert(seaweedfs.delete()) \ No newline at end of file diff --git a/tests/kind/whisk-affinity.yaml b/tests/kind/whisk-affinity.yaml index b6a02eae..2f56c7a5 100644 --- a/tests/kind/whisk-affinity.yaml +++ b/tests/kind/whisk-affinity.yaml @@ -38,9 +38,9 @@ spec: cron: false # tls enabled or not tls: false - # minio enabled or not - minio: true - # minio static enabled or not + # seaweedfs enabled or not + seaweedfs: true + # seaweedfs static enabled or not static: true # postgres enabled or not postgres: true @@ -109,21 +109,9 @@ spec: #prefix: openserverless password: s0meP@ass3 mongodb: - host: mongodb volume-size: 5 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 2 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/kind/whisk-full-affinity.yaml b/tests/kind/whisk-full-affinity.yaml index 96915494..4eb7c473 100644 --- a/tests/kind/whisk-full-affinity.yaml +++ b/tests/kind/whisk-full-affinity.yaml @@ -42,11 +42,11 @@ spec: cron: true # activate tls support tls: false - # minio enabled or not - minio: true + # seaweedfs enabled or not + seaweedfs: true # zookeeper enabled or not zookeeper: true - # minio static enabled or not + # seaweedfs static enabled or not static: true # prometheus monitoring enabled or not monitoring: true @@ -148,21 +148,9 @@ spec: prefix: openserverless password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 10 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/kind/whisk-full-mf.yaml b/tests/kind/whisk-full-mf.yaml index 55de7928..a63338c8 100644 --- a/tests/kind/whisk-full-mf.yaml +++ b/tests/kind/whisk-full-mf.yaml @@ -40,11 +40,11 @@ spec: cron: false # activate tls support tls: false - # minio enabled or not - minio: false + # seaweedfs enabled or not + seaweedfs: false # zookeeper enabled or not zookeeper: true - # minio static enabled or not + # seaweedfs static enabled or not static: false # prometheus monitoring enabled or not monitoring: false @@ -143,21 +143,9 @@ spec: prefix: openserverless password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 10 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/kind/whisk-full-storage.yaml b/tests/kind/whisk-full-storage.yaml index ef603cf3..5e957b1b 100644 --- a/tests/kind/whisk-full-storage.yaml +++ b/tests/kind/whisk-full-storage.yaml @@ -28,7 +28,7 @@ spec: cron: false invoker: true kafka: true - minio: true + seaweedfs: true mongodb: false monitoring: false openwhisk: true @@ -145,26 +145,14 @@ spec: prefix: openserverless password: s0meP@ass4 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: ingress: s3-enabled: true console-enabled: true s3-hostname: auto console-hostname: auto volume-size: 10 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/kind/whisk-full.yaml b/tests/kind/whisk-full.yaml index 20adf7c9..93ce25bb 100644 --- a/tests/kind/whisk-full.yaml +++ b/tests/kind/whisk-full.yaml @@ -40,11 +40,11 @@ spec: cron: true # activate tls support tls: false - # minio enabled or not - minio: true + # seaweedfs enabled or not + seaweedfs: true # zookeeper enabled or not zookeeper: true - # minio static enabled or not + # seaweedfs static enabled or not static: true # prometheus monitoring enabled or not monitoring: true @@ -161,26 +161,14 @@ spec: prefix: openserverless password: s0meP@ass4 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: ingress: s3-enabled: true console-enabled: true s3-hostname: auto console-hostname: auto volume-size: 10 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/kind/whisk-mf-demo.yaml b/tests/kind/whisk-mf-demo.yaml index ce9e4c70..3bbe6bce 100644 --- a/tests/kind/whisk-mf-demo.yaml +++ b/tests/kind/whisk-mf-demo.yaml @@ -38,9 +38,9 @@ spec: cron: false # tls enabled or not tls: false - # minio enabled or not - minio: false - # minio static enabled or not + # seaweedfs enabled or not + seaweedfs: false + # seaweedfs static enabled or not static: false # postgres enabled or not postgres: false @@ -112,21 +112,9 @@ spec: #prefix: openserverless password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 2 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/kind/whisk-minimal.yaml b/tests/kind/whisk-minimal.yaml index 9093c957..26d0295d 100644 --- a/tests/kind/whisk-minimal.yaml +++ b/tests/kind/whisk-minimal.yaml @@ -40,9 +40,7 @@ spec: cron: false # tls enabled or not tls: false - # minio enabled or not - minio: false - # minio static enabled or not + # seaweedfs static enabled or not static: true # postgres enabled or not postgres: false @@ -116,24 +114,7 @@ spec: #prefix: openserverless password: s0meP@ass3 mongodb: - host: mongodb volume-size: 5 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: - volume-size: 2 - admin: - user: minioadmin - password: minioadmin - openserverless: - user: openserverless - password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG milvus: volume-size: cluster: 20 diff --git a/tests/kind/whisk-slim.yaml b/tests/kind/whisk-slim.yaml index 352eafcc..5b67e51d 100644 --- a/tests/kind/whisk-slim.yaml +++ b/tests/kind/whisk-slim.yaml @@ -40,9 +40,7 @@ spec: cron: false # tls enabled or not tls: false - # minio enabled or not - minio: false - # minio static enabled or not + # seaweedfs static enabled or not static: true # postgres enabled or not postgres: true @@ -115,24 +113,7 @@ spec: #prefix: openserverless password: s0meP@ass3 mongodb: - host: mongodb volume-size: 5 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: - volume-size: 2 - admin: - user: minioadmin - password: minioadmin - openserverless: - user: openserverless - password: tfteSlswRu7BJ86wekitnifILbZam1KYY3TG postgres: volume-size: 5 replicas: 1 diff --git a/tests/kind/whisk.yaml b/tests/kind/whisk.yaml index e795d605..7fec1ae6 100644 --- a/tests/kind/whisk.yaml +++ b/tests/kind/whisk.yaml @@ -38,9 +38,9 @@ spec: cron: false # tls enabled or not tls: false - # minio enabled or not - minio: true - # minio static enabled or not + # seaweedfs enabled or not + seaweedfs: true + # seaweedfs static enabled or not static: true # postgres enabled or not postgres: false @@ -115,21 +115,9 @@ spec: #prefix: openserverless password: s0meP@ass3 mongodb: - host: mongodb volume-size: 2 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 2 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/lks/whisk-full.yaml b/tests/lks/whisk-full.yaml index 3c641075..1098b23e 100644 --- a/tests/lks/whisk-full.yaml +++ b/tests/lks/whisk-full.yaml @@ -40,9 +40,9 @@ spec: cron: true # enable TLS tls: true - # minio enabled or not - minio: true - # minio static enabled or not + # seaweedfs enabled or not + seaweedfs: true + # seaweedfs static enabled or not static: true # zookeeper enabled or not zookeeper: true @@ -104,21 +104,9 @@ spec: prefix: nuv password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 10 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/lks/whisk.yaml b/tests/lks/whisk.yaml index 3c813f6a..90a49649 100644 --- a/tests/lks/whisk.yaml +++ b/tests/lks/whisk.yaml @@ -40,9 +40,9 @@ spec: cron: true # enable TLS tls: true - # minio enabled or not - minio: true - # minio static enabled or not + # seaweedfs enabled or not + seaweedfs: true + # seaweedfs static enabled or not static: true # postgres enabled or not postgres: true @@ -89,21 +89,9 @@ spec: prefix: nuv password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 2 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/microk8s/whisk-full.yaml b/tests/microk8s/whisk-full.yaml index 2a5e9f0e..efd522ed 100644 --- a/tests/microk8s/whisk-full.yaml +++ b/tests/microk8s/whisk-full.yaml @@ -43,9 +43,9 @@ spec: cron: true # enable TLS tls: true - # minio enabled or not - minio: true - # minio static enabled or not + # seaweedfs enabled or not + seaweedfs: true + # seaweedfs static enabled or not static: true # zookeeper enabled or not zookeeper: true @@ -120,21 +120,9 @@ spec: prefix: nuv password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 10 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/microk8s/whisk.yaml b/tests/microk8s/whisk.yaml index 4bdba5e3..52403024 100644 --- a/tests/microk8s/whisk.yaml +++ b/tests/microk8s/whisk.yaml @@ -41,9 +41,9 @@ spec: cron: true # enable TLS tls: true - # minio enabled or not - minio: true - # minio static enabled or not + # seaweedfs enabled or not + seaweedfs: true + # seaweedfs static enabled or not static: true # postgres enabled or not postgres: true @@ -91,21 +91,9 @@ spec: prefix: nuv password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 2 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/openshift/minimal.yaml b/tests/openshift/minimal.yaml index cffa6ea4..d622faf7 100644 --- a/tests/openshift/minimal.yaml +++ b/tests/openshift/minimal.yaml @@ -43,8 +43,8 @@ spec: cron: false # enable TLS tls: false - # minio enabled or not - minio: false + # seaweedfs enabled or not + seaweedfs: false openwhisk: namespaces: whisk-system: 789c46b1-71f6-4ed5-8c54-816aa4f8c502:abcfO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP @@ -61,14 +61,7 @@ spec: user: controller_admin password: s0meP@ass2 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 kafka: host: kafka volume-size: 10 @@ -82,11 +75,11 @@ spec: tls: acme-registered-email: francesco@nuvolaris.io acme-server-url: https://acme-v02.api.letsencrypt.org/directory - minio: + seaweedfs: volume-size: 2 openserverless: - root-user: minioadmin - root-password: minioadmin + user: openserverless + password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG configs: limits: actions: diff --git a/tests/openshift/whisk-full.yaml b/tests/openshift/whisk-full.yaml index d55629e7..d1bad5be 100644 --- a/tests/openshift/whisk-full.yaml +++ b/tests/openshift/whisk-full.yaml @@ -41,9 +41,9 @@ spec: cron: true # tls enabled or not tls: true - # minio enabled or not - minio: true - # minio static enabled or not + # seaweedfs enabled or not + seaweedfs: true + # seaweedfs static enabled or not static: true # zookeeper enabled or not zookeeper: true @@ -92,21 +92,9 @@ spec: prefix: nuv password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 10 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/openshift/whisk.yaml b/tests/openshift/whisk.yaml index fbb19397..64511fc3 100644 --- a/tests/openshift/whisk.yaml +++ b/tests/openshift/whisk.yaml @@ -41,9 +41,9 @@ spec: cron: true # tls enabled or not tls: false - # minio enabled or not - minio: true - # minio static enabled or not + # seaweedfs enabled or not + seaweedfs: true + # seaweedfs static enabled or not static: true # postgres enabled or not postgres: true @@ -91,21 +91,9 @@ spec: prefix: nuv password: s0meP@ass3 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: + seaweedfs: volume-size: 2 - admin: - user: minioadmin - password: minioadmin openserverless: user: openserverless password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG diff --git a/tests/patcher/whisk-full-baupanlabs.yaml b/tests/patcher/whisk-full-baupanlabs.yaml index 94a97e57..dae663d8 100644 --- a/tests/patcher/whisk-full-baupanlabs.yaml +++ b/tests/patcher/whisk-full-baupanlabs.yaml @@ -38,14 +38,12 @@ spec: mongodb: true # start redis redis: true - # start s3ninja - s3bucket: false # start cron based action parser cron: true # enable TLS tls: true - # minio enabled or not - minio: true + # seaweedfs enabled or not + seaweedfs: true # zookeeper enabled or not zookeeper: true openwhisk: @@ -65,14 +63,7 @@ spec: user: controller_admin password: s0meP@ass2 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 kafka: host: kafka volume-size: 10 @@ -98,11 +89,11 @@ spec: tls: acme-registered-email: francesco@nuvolaris.io acme-server-url: https://acme-v02.api.letsencrypt.org/directory - minio: + seaweedfs: volume-size: 5 openserverless: - root-user: minioadmin - root-password: minioadmin + user: openserverless + password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG zookeeper: host: zookeeper data-volume-size: 10 diff --git a/tests/patcher/whisk-full-update.yaml b/tests/patcher/whisk-full-update.yaml index 1264e276..f7c79324 100644 --- a/tests/patcher/whisk-full-update.yaml +++ b/tests/patcher/whisk-full-update.yaml @@ -34,14 +34,12 @@ spec: mongodb: true # start redis redis: true - # start s3ninja - s3bucket: false # start cron based action parser cron: true # activate tls support tls: false - # minio enabled or not - minio: true + # seaweedfs enabled or not + seaweedfs: true # zookeeper enabled or not zookeeper: true openwhisk: @@ -62,14 +60,7 @@ spec: user: controller_admin password: s0meP@ass2 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 exposed: true kafka: host: kafka @@ -93,11 +84,11 @@ spec: tag: "0.3.0-morpheus.22122609" scheduler: schedule: "* * * * *" - minio: + seaweedfs: volume-size: 2 openserverless: - root-user: minioadmin - root-password: minioadmin + user: openserverless + password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG zookeeper: host: zookeeper data-volume-size: 5 diff --git a/tests/patcher/whisk-update.yaml b/tests/patcher/whisk-update.yaml index dfdc565c..32aa48f4 100644 --- a/tests/patcher/whisk-update.yaml +++ b/tests/patcher/whisk-update.yaml @@ -36,14 +36,12 @@ spec: mongodb: true # start redis redis: true - # start s3ninja - s3bucket: false # start cron based action parser cron: true # tls enabled or not tls: false - # minio enabled or not - minio: true + # seaweedfs enabled or not + seaweedfs: true openwhisk: namespaces: whisk-system: 789c46b1-71f6-4ed5-8c54-816aa4f8c502:abczO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP @@ -62,16 +60,7 @@ spec: user: controller_admin password: s0meP@ass2 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False kafka: host: kafka volume-size: 10 @@ -85,11 +74,11 @@ spec: tls: acme-registered-email: francesco@nuvolaris.io acme-server-url: https://acme-staging-v02.api.letsencrypt.org/directory - minio: + seaweedfs: volume-size: 2 openserverless: - root-user: minioadmin - root-password: minioadmin + user: openserverless + password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG configs: limits: actions: diff --git a/tests/redis_tenant_acl_live.py b/tests/redis_tenant_acl_live.py new file mode 100644 index 00000000..78855855 --- /dev/null +++ b/tests/redis_tenant_acl_live.py @@ -0,0 +1,145 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +"""Explicit lab-only lifecycle validation; never run by unit-test discovery.""" +import json +import logging +import secrets +import sys +from urllib.parse import unquote, urlsplit + + +def run(): + import redis as driver + from openserverless import config as cfg, kube, redis as ops + from openserverless.redis_client import RedisClient + from openserverless.user_config import UserConfig + from openserverless.user_metadata import UserMetadata + cfg.configure(kube.get('wsk/controller')['spec']);cfg.detect() + admin=ops.admin_client();name='racl'+secrets.token_hex(5);peer=name+'peer' + prefix=name+':';other_prefix=peer+':' + password=secrets.token_urlsafe(20);peer_password=secrets.token_urlsafe(20) + clients=[];checks={} + def check(label,value): + checks[label]=bool(value) + if not value:raise AssertionError(label) + def client(user,secret): + value=driver.Redis(host='redis',username=user,password=secret,decode_responses=True,socket_timeout=5) + clients.append(value);return value + def denied(fn): + try:fn();return False + except driver.exceptions.NoPermissionError:return True + def config(user,secret):return UserConfig({'namespace':user,'password':secret,'redis':{'enabled':True,'password':secret,'prefix':user+':'}}) + def denied_admin(c,user): + check(user+'_acl_denied',denied(c.acl_users)) + check(user+'_scan_denied',denied(lambda:c.scan(0))) + for command in [('SCRIPT','FLUSH'),('SCRIPT','KILL'),('SCRIPT','DEBUG','NO'),('FUNCTION','FLUSH'),('ACL','SETUSER',name+'nevercreated','on','nopass','+@all','~*'),('CONFIG','GET','*'),('FLUSHALL',),('SELECT',1)]: + result=admin.execute_command('ACL','DRYRUN',user,*command) + check(user+'_dryrun_'+':'.join(map(str,command[:2])),'no permissions' in result.lower()) + try: + u=config(name,password);metadata=UserMetadata(u) + check('public_tenant_create',ops.create_db_user(u,metadata)) + fields={v['key']:v['value'] for v in metadata._data['metadata']} + check('tenant_profile_bound',fields['REDIS_PREFIX']==prefix and unquote(urlsplit(fields['REDIS_URL']).username)==name) + check('peer_create',ops.provision_user(peer,peer_password,other_prefix)) + a=client(name,password);b=client(peer,peer_password);key=prefix+'opsval:lifecycle';other=other_prefix+'opsval:lifecycle' + check('own_write',a.set(key,'owned',ex=120));b.set(other,'peer',ex=120) + check('own_lua',a.eval("return redis.call('GET',KEYS[1])",1,key)=='owned') + check('peer_read_denied',denied(lambda:a.get(other))) + check('peer_write_denied',denied(lambda:a.set(other,'unexpected'))) + denied_admin(a,name) + # Reconciliation must remove a previously broad command grant. + admin.execute_command('ACL','SETUSER',name,'+@all') + check('public_reconcile',ops.create_db_user(u,UserMetadata(u))) + denied_admin(a,name) + # The legacy backport must also replace old key/channel grants and selectors. + if not hasattr(ops,'ADMIN_USER'): + admin.execute_command('ACL','SETUSER',name,'+@all','~*','&*','(+@all ~* &*)') + check('stale_grants_reconciled',ops.create_db_user(u,UserMetadata(u))) + actual=admin.acl_getuser(name) + check('stale_selectors_removed',not actual.get('selectors')) + check('stale_global_keys_removed',actual.get('keys')==['~'+prefix+'*']) + check('stale_global_channels_removed',actual.get('channels')==['&'+prefix+'*']) + check('peer_still_denied_after_reconcile',denied(lambda:a.get(other))) + hashes=admin.acl_getuser(name)['passwords'] + q=RedisClient.__new__(RedisClient) + check('quota_readonly',q.set_prefix_readonly(name,prefix)) + check('quota_credentials_preserved',admin.acl_getuser(name)['passwords']==hashes) + check('quota_lua_read',a.eval("return redis.call('GET',KEYS[1])",1,key)=='owned') + check('quota_write_denied',denied(lambda:a.set(key,'unexpected'))) + check('quota_restore',q.set_prefix_all(name,prefix)) + check('quota_write_restored',a.set(key,'restored',ex=120)) + denied_admin(a,name) + ro_name=name+'ro';ro_cfg=config(ro_name,password) + check('create_readonly',ops.create_db_user(ro_cfg,UserMetadata(ro_cfg),True)) + ro=client(ro_name,password) + check('new_readonly_write_denied',denied(lambda:ro.set(ro_name+':opsval:test','unexpected'))) + check('new_readonly_lua_available',ro.eval('return 1',0)==1) + check('delete_readonly_user',ops.delete_db_user(ro_name)) + # Pub/Sub must remain usable only in the tenant's channel prefix. + ps=a.pubsub();ps.subscribe(prefix+'events');ps.get_message(timeout=1);a.publish(prefix+'events','synthetic') + msg=ps.get_message(timeout=2);check('own_pubsub',msg and msg.get('data')=='synthetic');ps.close() + check('peer_pubsub_denied',denied(lambda:a.publish(other_prefix+'events','unexpected'))) + # Spaces and ACL-looking characters are password data, never ACL syntax. + special='synthetic +@all / @ '+secrets.token_hex(8) + rotated=config(name,special);new_metadata=UserMetadata(rotated) + check('public_password_rotation',ops.create_db_user(rotated,new_metadata)) + if hasattr(ops,'ADMIN_USER'): + try:client(name,password).ping();rejected=False + except driver.exceptions.AuthenticationError:rejected=True + check('advanced_old_password_rejected',rejected) + try:a.get(key);old_session_denied=False + except (driver.exceptions.AuthenticationError,driver.exceptions.ConnectionError):old_session_denied=True + check('advanced_old_session_revoked',old_session_denied) + else: + check('legacy_existing_password_preserved',client(name,password).get(key)=='restored') + check('legacy_existing_session_preserved',a.get(key)=='restored') + params={v['key']:v['value'] for v in new_metadata._data['metadata']} + c=driver.Redis.from_url(params['REDIS_URL'],decode_responses=True,socket_timeout=5);clients.append(c) + check('encoded_profile_password_works',c.get(key)=='restored') + denied_admin(c,name) + check('delete_tenant',ops.delete_db_user(name)) + try:client(name,special).ping();deleted=False + except driver.exceptions.AuthenticationError:deleted=True + check('deleted_identity_rejected',deleted) + check('deletion_retains_existing_data',admin.get(key)=='restored') + for bad in ['*','team:*','team: +@all','team?']: + try:ops.provision_user(name,password,bad);rejected=False + except ValueError:rejected=True + check('invalid_prefix_'+str(len(checks)),rejected) + except Exception as exc: + checks['completed_without_unexpected_'+type(exc).__name__]=False + finally: + for c in clients: + try:c.close() + except Exception:pass + for user in [name,peer,name+'ro']: + try:ops.delete_db_user(user) + except Exception:checks['cleanup_identity']=False + for pref in [prefix,other_prefix,name+'ro:']: + keys=list(admin.scan_iter(match=pref+'*')) + if keys:admin.delete(*keys) + admin.close() + return {'checks':checks,'passed':sum(checks.values()),'failed':sum(not v for v in checks.values())} + + +if __name__=='__main__': + if sys.argv[1:]!=['--lab']: + raise SystemExit('Explicit --lab is required; synthetic identities will be created and removed.') + logging.disable(logging.CRITICAL) + value=run();print(json.dumps(value));raise SystemExit(bool(value['failed'])) diff --git a/tests/minio-nuv.js b/tests/s3-nuv.js similarity index 70% rename from tests/minio-nuv.js rename to tests/s3-nuv.js index f9bb64ef..cdf4c192 100644 --- a/tests/minio-nuv.js +++ b/tests/s3-nuv.js @@ -19,28 +19,28 @@ const Minio = require('minio'); async function main(args) { - console.log(`connecting to ${args.minio_host}:${args.minio_port}`) - let minioClient = new Minio.Client({ - endPoint: args.minio_host, - port: args.minio_port, + console.log(`connecting to ${args.s3_host}:${args.s3_port}`) + let s3Client = new Minio.Client({ + endPoint: args.s3_host, + port: args.s3_port, useSSL: false, - accessKey: args.minio_access, - secretKey: args.minio_secret + accessKey: args.s3_access, + secretKey: args.s3_secret }); let response = {}; - let bucketName = args.minio_data; + let bucketName = args.s3_data; console.log(`checking existence of bucket ${bucketName}`) - let bucketExists = await minioClient.bucketExists(bucketName); + let bucketExists = await s3Client.bucketExists(bucketName); console.log(`bucket ${bucketName} exists ${bucketExists}`) if(!bucketExists) { console.log(`creating missing bucket ${bucketName}`) - response.bucketOperation = await minioClient.makeBucket(bucketName, 'us-east-1'); + response.bucketOperation = await s3Client.makeBucket(bucketName, 'us-east-1'); } - response.buckets = await minioClient.listBuckets(); + response.buckets = await s3Client.listBuckets(); return { "body": response } diff --git a/tests/minio.js b/tests/s3.js similarity index 70% rename from tests/minio.js rename to tests/s3.js index 81b155df..40a61afc 100644 --- a/tests/minio.js +++ b/tests/s3.js @@ -19,26 +19,26 @@ const Minio = require('minio'); async function main(args) { - console.log(`connecting to ${args.minio_host}:${args.minio_port}`) - let minioClient = new Minio.Client({ - endPoint: args.minio_host, - port: args.minio_port, + console.log(`connecting to ${args.s3_host}:${args.s3_port}`) + let s3Client = new Minio.Client({ + endPoint: args.s3_host, + port: args.s3_port, useSSL: false, - accessKey: args.minio_user, - secretKey: args.minio_pwd + accessKey: args.s3_user, + secretKey: args.s3_pwd }); let response = {}; let bucketName = 'test-bucket'; - let bucketExists = await minioClient.bucketExists(bucketName); + let bucketExists = await s3Client.bucketExists(bucketName); console.log(`${bucketName} exists ${bucketExists}`); if(!bucketExists) { - response.bucketOperation = await minioClient.makeBucket(bucketName, 'us-east-1'); + response.bucketOperation = await s3Client.makeBucket(bucketName, 'us-east-1'); } - response.buckets = await minioClient.listBuckets(); + response.buckets = await s3Client.listBuckets(); return { "body": response } diff --git a/tests/test_actionexecutor_cron_security.py b/tests/test_actionexecutor_cron_security.py new file mode 100644 index 00000000..94721a11 --- /dev/null +++ b/tests/test_actionexecutor_cron_security.py @@ -0,0 +1,112 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import json +import unittest +from unittest.mock import Mock, patch + +import openserverless.actionexecutor as executor + + +def action(namespace, name, annotations): + return { + "name": name, + "entityType": "action", + "namespace": namespace, + "parameters": [], + "annotations": annotations, + } + + +class CronTenantIsolationTest(unittest.TestCase): + def test_invalid_cron_values_are_skipped_before_parsing(self): + for value in (None, False, 42, [], {}, "", "x" * 4097): + with self.subTest(value=value): + with patch.object(executor.cn.croniter, "is_valid") as is_valid, self.assertLogs(level="WARNING"): + result = executor.should_trigger( + "tenant-a", "broken", value, executor.datetime.now(), 60 + ) + self.assertEqual(result, "no_execution") + is_valid.assert_not_called() + + def test_malformed_actions_do_not_stop_other_tenants(self): + actions = [ + action("tenant-a", "null-cron", [{"key": "cron", "value": None}]), + action("tenant-a", "missing-value", [{"key": "cron"}]), + action("tenant-b", "healthy", [{"key": "cron", "value": "* * * * *"}]), + action("tenant-b", "one-shot", [{"key": "autoexec", "value": True}]), + ] + subjects = [ + {"name": "tenant-a", "uuid": "a", "key": "a-secret"}, + {"name": "tenant-b", "uuid": "b", "key": "b-secret"}, + ] + config = { + "scheduler.schedule": "* * * * *", + "controller.protocol": "http", + "controller.host": "controller", + "controller.port": "3233", + "couchdb.controller.user": "controller", + "couchdb.controller.password": "secret", + } + db = Mock() + db.wait_db_ready.return_value = True + batches = { + "whisks": [ + {"docs": actions, "bookmark": "next-actions"}, + {"docs": [], "bookmark": ""}, + ], + "subjects": [ + {"docs": [{"namespaces": subjects}], "bookmark": "next-subjects"}, + {"docs": [], "bookmark": ""}, + ], + } + + def find_doc(database, selector, username, password): + self.assertEqual((username, password), ("controller", "secret")) + self.assertIn("selector", json.loads(selector)) + return batches[database].pop(0) + + db.find_doc.side_effect = find_doc + + with ( + patch.dict(executor.os.environ, {"SCHEDULER_CONFIG": json.dumps(config)}), + patch.object(executor.cu, "CouchDB", return_value=db), + patch.object(executor.req, "post", return_value=Mock(status_code=202, text="")) as invoke, + patch.object(executor.req, "put", return_value=Mock(status_code=200, text="")) as unschedule, + self.assertLogs(level="WARNING") as logs, + ): + executor.start() + + self.assertTrue(any("Failed to process scheduled action" in line for line in logs.output)) + self.assertEqual(invoke.call_count, 2) + self.assertEqual( + [call.args[0] for call in invoke.call_args_list], + [ + "http://controller:3233/api/v1/namespaces/tenant-b/actions/healthy?blocking=false&result=false", + "http://controller:3233/api/v1/namespaces/tenant-b/actions/one-shot?blocking=false&result=false", + ], + ) + self.assertEqual( + [call.kwargs["auth"] for call in invoke.call_args_list], + [("b", "b-secret"), ("b", "b-secret")], + ) + unschedule.assert_called_once() + self.assertEqual(unschedule.call_args.kwargs["auth"], ("b", "b-secret")) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_alertmanager_config.py b/tests/test_alertmanager_config.py new file mode 100644 index 00000000..021e43c1 --- /dev/null +++ b/tests/test_alertmanager_config.py @@ -0,0 +1,37 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +import itertools +import unittest +from pathlib import Path +import jinja2 +import yaml + +class AlertmanagerConfigTests(unittest.TestCase): + def test_receiver_exists_for_every_notification_combination(self): + template = jinja2.Template((Path(__file__).resolve().parents[1] / "openserverless/templates/alert-manager-02-cm.yaml").read_text()) + for slack, gmail, slack_default, gmail_default in itertools.product([False, True], repeat=4): + with self.subTest(slack=slack, gmail=gmail, slack_default=slack_default, gmail_default=gmail_default): + rendered = template.render(slack=slack, gmail=gmail, slack_default=slack_default, gmail_default=gmail_default, + slack_api_url="https://example.test", slack_channel_name="#test", email_recipients="test@example.test", + email_from="test@example.test", gmail_username="test@example.test", gmail_password="test") + config = yaml.safe_load(yaml.safe_load(rendered)["data"]["alertmanager.yml"]) + names = {r["name"] for r in config["receivers"]} + expected = "email-receiver" if gmail and gmail_default else "slack-notifications" if slack and slack_default else "discard" + self.assertEqual(config["route"]["receiver"], expected) + for route in config["route"].get("routes", []): self.assertIn(route["receiver"], names) + discard = next(r for r in config["receivers"] if r["name"] == "discard") + self.assertEqual(discard, {"name": "discard"}) diff --git a/tests/test_couchdb_metadata_security.py b/tests/test_couchdb_metadata_security.py new file mode 100644 index 00000000..71359360 --- /dev/null +++ b/tests/test_couchdb_metadata_security.py @@ -0,0 +1,54 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import unittest +from unittest.mock import Mock, patch +from openserverless import couchdb +from openserverless.couchdb_util import CouchDB + +class MetadataSecurityTest(unittest.TestCase): + @patch.object(couchdb.cfg, "get", return_value="configured_admin") + def test_new_and_existing_database_are_restricted_without_recreation(self, config): + for exists in (False, True): + with self.subTest(exists=exists): + db = Mock() + db.check_db.return_value = exists + self.assertTrue(couchdb.init_users_metadata(db)) + db.add_role.assert_called_once_with("users_metadata", ["configured_admin"]) + if exists: + db.create_db.assert_not_called() + else: + db.create_db.assert_called_once_with("users_metadata") + db.delete_db.assert_not_called() + + @patch.object(couchdb.cfg, "get", return_value="configured_admin") + def test_acl_failure_is_not_reported_as_success(self, config): + db = Mock() + db.check_db.return_value = True + db.add_role.return_value = False + self.assertFalse(couchdb.init_users_metadata(db)) + + def test_security_document_uses_explicit_members_without_public_roles(self): + db = CouchDB.__new__(CouchDB) + db.db_base = "http://couchdb:5984/openserverless_" + db.db_session = Mock() + db.db_session.put.return_value.status_code = 200 + self.assertTrue(db.add_role("users_metadata", ["configured_admin"])) + db.db_session.put.assert_called_once_with( + "http://couchdb:5984/openserverless_users_metadata/_security", + json={"admins":{"names":[],"roles":[]}, + "members":{"names":["configured_admin"],"roles":[]}}) diff --git a/tests/test_datastore_connection.py b/tests/test_datastore_connection.py new file mode 100644 index 00000000..dd0fea99 --- /dev/null +++ b/tests/test_datastore_connection.py @@ -0,0 +1,100 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +import io +import os +import subprocess +import unittest +from unittest.mock import MagicMock, patch + +from openserverless import datastore_connection as connection + + +class PrivateServiceConnectionTests(unittest.TestCase): + def process(self, line='Forwarding from 127.0.0.1:41237 -> 2379\n'): + process = MagicMock() + process.stdout = io.StringIO(line) + process.poll.return_value = None + return process + + def test_in_cluster_uses_service_without_starting_a_tunnel(self): + with patch.dict(os.environ, {'KUBERNETES_SERVICE_HOST': 'cluster'}, clear=True), \ + patch.object(connection.subprocess, 'Popen') as spawn: + with connection.service_endpoint('openserverless-etcd', 2379) as endpoint: + self.assertEqual(endpoint, ('openserverless-etcd', 2379)) + spawn.assert_not_called() + def test_external_tunnel_binds_only_loopback_and_closes(self): + process = self.process() + with patch.dict(os.environ, {}, clear=True), \ + patch.object(connection.subprocess, 'Popen', return_value=process) as spawn, \ + patch.object(connection.selectors, 'DefaultSelector') as selector: + selector.return_value.__enter__.return_value.select.return_value = [object()] + with connection.service_endpoint('openserverless-etcd', 2379) as endpoint: + self.assertEqual(endpoint, ('127.0.0.1', 41237)) + command = spawn.call_args.args[0] + self.assertIn('--address=127.0.0.1', command) + self.assertIn('service/openserverless-etcd', command) + self.assertIn(':2379', command) + self.assertFalse(any('password' in item for item in command)) + process.terminate.assert_called_once() + self.assertTrue(process.stdout.closed) + + def test_consumer_error_still_closes_tunnel(self): + process = self.process() + with patch.dict(os.environ, {}, clear=True), \ + patch.object(connection.subprocess, 'Popen', return_value=process), \ + patch.object(connection.selectors, 'DefaultSelector') as selector: + selector.return_value.__enter__.return_value.select.return_value = [object()] + with self.assertRaisesRegex(ValueError, 'consumer failed'): + with connection.service_endpoint('openserverless-etcd', 2379): + raise ValueError('consumer failed') + process.terminate.assert_called_once() + self.assertTrue(process.stdout.closed) + + def test_failed_tunnel_does_not_report_process_output(self): + process = self.process('credential-bearing diagnostic must stay private\n') + process.poll.return_value = 1 + with patch.dict(os.environ, {}, clear=True), \ + patch.object(connection.subprocess, 'Popen', return_value=process), \ + patch.object(connection.selectors, 'DefaultSelector') as selector: + selector.return_value.__enter__.return_value.select.return_value = [object()] + with self.assertRaisesRegex(RuntimeError, '^Could not open the private datastore tunnel$'): + with connection.service_endpoint('openserverless-etcd', 2379): + self.fail('A failed tunnel must not yield an endpoint') + + def test_a_stuck_tunnel_is_killed_during_cleanup(self): + process = self.process() + process.wait.side_effect = [subprocess.TimeoutExpired('kubectl', 5), None] + with patch.dict(os.environ, {}, clear=True), \ + patch.object(connection.subprocess, 'Popen', return_value=process), \ + patch.object(connection.selectors, 'DefaultSelector') as selector: + selector.return_value.__enter__.return_value.select.return_value = [object()] + with connection.service_endpoint('openserverless-etcd', 2379): + pass + process.kill.assert_called_once() + self.assertTrue(process.stdout.closed) + + def test_invalid_targets_cannot_be_kubectl_options(self): + with patch.object(connection.subprocess, 'Popen') as spawn: + for service, port in [('--all-namespaces', 2379), ('service;command', 2379), + ('redis', 0), ('redis', 65536)]: + with self.assertRaises(ValueError): + with connection.service_endpoint(service, port): + self.fail('Invalid target accepted') + spawn.assert_not_called() diff --git a/tests/test_datastore_security.py b/tests/test_datastore_security.py new file mode 100644 index 00000000..5b0701de --- /dev/null +++ b/tests/test_datastore_security.py @@ -0,0 +1,178 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import base64 +import json +import logging +from pathlib import Path +import subprocess +import unittest +from unittest.mock import patch + +from openserverless import config, datastore_security as security + + +class SecurityTests(unittest.TestCase): + def test_debug_logs_never_contain_values(self): + config.clean() + config.configure({'etcd': {'root': {'password': 'sentinel-password'}}, + 'opaque': {'url': 'http://user:sentinel-uri@internal'}, + 'state': {'spec': {'secret': 'sentinel-nested'}}}) + with self.assertLogs(level=logging.DEBUG) as logs: + config.dump_config() + text = '\n'.join(logs.output) + for value in ['sentinel-password', 'sentinel-uri', 'sentinel-nested']: + self.assertNotIn(value, text) + self.assertIn('etcd.root.password', text) + + def test_private_apply_does_not_report_stderr_or_payload(self): + spec = {'kind': 'Secret', 'stringData': {'password': 'sentinel'}} + with patch.object(security.subprocess, 'run', return_value= + subprocess.CompletedProcess([], 1, 'sentinel', 'sentinel')): + with self.assertRaisesRegex(RuntimeError, '^Could not apply private datastore resources$'): + security.apply_private(spec) + + def test_secrets_do_not_reach_public_kubectl_wrapper(self): + secret = {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'private'}} + service = {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'public'}} + with patch.object(security, 'apply_private') as private, patch.object(security.kube, 'apply') as public: + security.apply({'items': [secret, service]}) + self.assertEqual(private.call_args.args[0]['items'], [secret]) + self.assertEqual(public.call_args.args[0]['items'], [service]) + + def test_fresh_signing_keys_are_unique_and_private(self): + captured = [] + with patch.object(security.kube, 'kubectl', return_value=''), \ + patch.object(security, 'apply_private', side_effect=captured.append): + security.ensure_jwt_key() + security.ensure_jwt_key() + keys = [base64.b64decode(x['data']['jwt-token.pem']) for x in captured] + self.assertNotEqual(keys[0], keys[1]) + self.assertTrue(all(b'PRIVATE KEY' in key for key in keys)) + + def test_existing_key_is_preserved(self): + key = subprocess.run(['openssl', 'genpkey', '-algorithm', 'RSA', + '-pkeyopt', 'rsa_keygen_bits:2048'], capture_output=True, check=True).stdout + raw = json.dumps({'data': {'jwt-token.pem': base64.b64encode(key).decode()}}) + with patch.object(security.kube, 'kubectl', return_value=raw), \ + patch.object(security, 'apply_private') as apply: + security.ensure_jwt_key() + apply.assert_not_called() + + def test_invalid_key_is_not_replaced(self): + raw = json.dumps({'data': {'jwt-token.pem': base64.b64encode(b'not-a-key').decode()}}) + with patch.object(security.kube, 'kubectl', return_value=raw), \ + patch.object(security, 'apply_private') as apply: + with self.assertRaises(RuntimeError): + security.ensure_jwt_key() + apply.assert_not_called() + + def test_missing_key_on_patched_cluster_is_not_reset(self): + sts = {'spec': {'template': {'spec': {'volumes': [ + {'secret': {'secretName': 'openserverless-etcd-jwt-token-v2'}}]}}}} + with patch.object(security.kube, 'kubectl', side_effect=['', json.dumps(sts)]), \ + patch.object(security, 'apply_private') as apply: + with self.assertRaises(RuntimeError): + security.ensure_jwt_key() + apply.assert_not_called() + + def test_monitoring_does_not_allow_all_peers(self): + rules = security.policy('milvus')['spec']['ingress'] + metrics = [rule for rule in rules if any(p['port'] == 9091 for p in rule['ports'])] + self.assertEqual(len(metrics), 1) + self.assertTrue(all(peer['podSelector']['matchLabels'] for peer in metrics[0]['from'])) + self.assertFalse(any('namespaceSelector' in peer for peer in metrics[0]['from'])) + + def test_etcd_peer_port_is_restricted_to_members(self): + rules = security.policy('etcd')['spec']['ingress'] + peer_rule = [rule for rule in rules if any(p['port'] == 2380 for p in rule['ports'])] + self.assertEqual(len(peer_rule), 1) + self.assertEqual(peer_rule[0]['from'], [{'podSelector': {'matchLabels': { + 'app.kubernetes.io/name': 'openserverless-etcd'}}}]) + + def test_secret_templates_cover_full_and_slim(self): + import jinja2 + import jinja2.meta + import yaml + root = Path(__file__).resolve().parents[1] + for mode in ['base', 'slim-base']: + path = root/'openserverless/templates'/('milvus-cfg-'+mode+'.yaml') + source = path.read_text() + variables = jinja2.meta.find_undeclared_variables(jinja2.Environment().parse(source)) + data = {name: 'sentinel' for name in variables} + data.update(etcd_range=[0, 1, 2], milvus_etcd_username='etcdmilvus', + milvus_etcd_password='punctuation:"\\\n', milvus_max_database_num=64, + milvus_max_role_num=128, milvus_max_user_num=128) + obj = yaml.safe_load(jinja2.Template(source).render(**data)) + self.assertEqual(obj['kind'], 'Secret') + cfg = yaml.safe_load(obj['stringData']['default.yaml']) + self.assertEqual(cfg['etcd']['auth']['userName'], 'etcdmilvus') + self.assertEqual(cfg['etcd']['auth']['password'], data['milvus_etcd_password']) + self.assertNotEqual(cfg['etcd']['auth']['userName'], 'root') + + def test_existing_system_user_is_verified_without_recreation(self): + from openserverless import milvus_standalone as milvus + data = {'openserverless_password': 'synthetic'} + with patch.object(milvus, 'MilvusAdminClient') as admin, \ + patch('openserverless.milvus_simple_client.MilvusSimpleClient') as client, \ + patch('requests.post') as post, patch.object(milvus, '_annotate_nuv_milvus_metadata'): + client.return_value.list_users.return_value = ['root', 'openserverless'] + post.return_value.ok = True + post.return_value.json.return_value = {'code': 0, 'data': []} + self.assertTrue(milvus.create_default_milvus_database(data)) + admin.return_value.setup_user.assert_not_called() + post.return_value.json.return_value = {'code': 1800, 'message': 'denied'} + self.assertFalse(milvus.create_default_milvus_database(data)) + admin.return_value.setup_user.assert_not_called() + + def test_scoped_account_accepts_only_its_prefix_and_exact_health_read(self): + import contextlib + try: + from openserverless import etcd_admin as admin + except ImportError: + from openserverless import etcd as admin + def operation(session, endpoint, path, body, **kwargs): + if path == 'auth/user/get': return {'roles': ['probe_role']} + if path == 'auth/role/get': return {'perm': [ + {'key': base64.b64encode(b'health').decode()}, + {'permType': 'READWRITE', 'key': base64.b64encode(b'milvus/').decode(), + 'range_end': base64.b64encode(b'milvus0').decode()}]} + return {} + with patch.object(admin, '_admin', return_value=contextlib.nullcontext((object(), 'endpoint'))), \ + patch.object(admin, '_post', side_effect=operation): + self.assertTrue(admin.create_etcd_user('probe', 'quoted:\"\n', 'milvus', readonly_keys=('health',))) + # The existing health permission is not silently accepted for a generic user. + self.assertFalse(admin.create_etcd_user('probe', 'quoted', 'milvus')) + + def test_reconciliation_refuses_an_existing_root_role(self): + import contextlib + try: + from openserverless import etcd_admin as admin + except ImportError: + from openserverless import etcd as admin + def operation(session, endpoint, path, body, **kwargs): + if path == 'auth/user/get': return {'roles': ['root', 'probe_role']} + if path == 'auth/role/get': return {'perm': []} + return {} + with patch.object(admin, '_admin', return_value=contextlib.nullcontext((object(), 'endpoint'))), \ + patch.object(admin, '_post', side_effect=operation): + self.assertFalse(admin.create_etcd_user('probe', 'synthetic', 'milvus')) + + +if __name__ == '__main__': + import jinja2.meta + unittest.main() diff --git a/tests/test_ferretdb_provisioning.py b/tests/test_ferretdb_provisioning.py new file mode 100644 index 00000000..3bda0ee3 --- /dev/null +++ b/tests/test_ferretdb_provisioning.py @@ -0,0 +1,105 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +"""Exercise FerretDB provisioning with the real namespace validator and renderer.""" +from contextlib import ExitStack +from types import SimpleNamespace +import unittest +from unittest.mock import Mock, patch +from urllib.parse import urlsplit, unquote + +from openserverless import ferretdb as mongo, postgres_operator as postgres, util + + +class FerretDBProvisioningTests(unittest.TestCase): + def setUp(self): + stack=ExitStack();self.addCleanup(stack.close) + self.values={'namespace':'fixtureuser','mongodb.database':'fixturedb','mongodb.password':'synthetic'} + self.user=SimpleNamespace(get=self.values.get) + self.metadata=Mock() + stack.enter_context(patch.object(util,'get_postgres_config_data',return_value={})) + self.primary=stack.enter_context(patch.object(util,'get_pod_name_by_selector',return_value='postgres-primary')) + stack.enter_context(patch.object(util,'get_pod_name',side_effect=AssertionError('ambiguous pod lookup'))) + self.service=stack.enter_context(patch.object(util,'get_service',return_value={'metadata':{'name':'mongo-svc','namespace':'openserverless'}})) + stack.enter_context(patch.object(mongo,'get_ferret_pod_name',return_value='mongo-0')) + # Keep the actual renderer and validator; mock only filesystem and Kubernetes I/O. + self.spool=stack.enter_context(patch.object(postgres.ntp,'spool_template',side_effect=lambda template,path,data:path)) + self.chmod=stack.enter_context(patch.object(postgres.os,'chmod')) + self.render=stack.enter_context(patch.object(postgres,'render_postgres_script',wraps=postgres.render_postgres_script)) + self.sql=stack.enter_context(patch.object(postgres,'exec_psql_command',return_value='SQL completed')) + + def assert_primary_and_names(self,mode): + self.primary.assert_called_once_with('app=openserverless-postgres',"{.items[?(@.metadata.labels.replicationRole == 'primary')].metadata.name}") + self.sql.assert_called_once() + self.assertEqual(self.sql.call_args.args[0],'postgres-primary') + self.assertEqual(len(self.render.call_args_list),2) + for call in self.render.call_args_list: + namespace,template,data=call.args + self.assertEqual(namespace,'fixtureuser') + self.assertEqual(data['database'],'fixturedb_ferretdb') + self.assertEqual(data['username'],'fixtureuser_ferretdb') + self.assertEqual(data['mode'],mode) + + def test_create_keeps_sql_suffix_and_uses_real_kubernetes_namespace(self): + self.assertTrue(mongo.create_db_user(self.user,self.metadata)) + self.assert_primary_and_names('create') + name,uri=self.metadata.add_metadata.call_args.args + self.assertEqual(name,'MONGODB_URL') + self.assertEqual(unquote(urlsplit(uri).username),'fixtureuser_ferretdb') + self.assertEqual(urlsplit(uri).path,'/fixturedb_ferretdb') + + def test_delete_uses_primary_and_the_same_existing_sql_names(self): + self.assertTrue(mongo.delete_db_user('fixtureuser','fixturedb')) + self.assert_primary_and_names('delete') + + def test_invalid_database_is_rejected_before_rendering_or_sql(self): + self.values['mongodb.database']='bad;name' + self.assertFalse(mongo.create_db_user(self.user,self.metadata)) + self.render.assert_not_called();self.sql.assert_not_called() + + def test_invalid_delete_database_is_rejected_before_rendering_or_sql(self): + self.assertFalse(mongo.delete_db_user('fixtureuser','bad;name')) + self.render.assert_not_called();self.sql.assert_not_called() + + def test_real_namespace_validation_remains_strict(self): + self.assertFalse(util.validate_namespace('fixtureuser_ferretdb')) + self.values['namespace']='fixture_user' + self.assertFalse(mongo.create_db_user(self.user,self.metadata)) + self.render.assert_not_called() + self.sql.assert_not_called() + + def test_invalid_delete_namespace_is_rejected_before_rendering_or_sql(self): + self.assertFalse(mongo.delete_db_user('fixture_user','fixturedb')) + self.render.assert_not_called();self.sql.assert_not_called() + + def test_failed_sql_does_not_publish_connection_metadata(self): + self.sql.return_value='' + self.assertFalse(mongo.create_db_user(self.user,self.metadata)) + self.metadata.add_metadata.assert_not_called() + + def test_missing_service_does_not_report_provisioning_success(self): + self.service.return_value=None + self.assertFalse(mongo.create_db_user(self.user,self.metadata)) + self.metadata.add_metadata.assert_not_called() + + def test_metadata_publication_failure_does_not_report_success(self): + self.metadata.add_metadata.side_effect=RuntimeError('synthetic metadata failure') + self.assertFalse(mongo.create_db_user(self.user,self.metadata)) + + +if __name__=='__main__':unittest.main() diff --git a/tests/test_messaging_security.py b/tests/test_messaging_security.py new file mode 100644 index 00000000..211de31c --- /dev/null +++ b/tests/test_messaging_security.py @@ -0,0 +1,278 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Messaging isolation contract and component lifecycle regression tests.""" + +import copy +from pathlib import Path +import unittest +from unittest.mock import patch + +import yaml + +from openserverless import config, kafka, main, messaging_security, zookeeper + +ROOT = Path(__file__).resolve().parents[1] + + +def policy(component): + return yaml.safe_load(next((ROOT / 'deploy' / component).glob('*networkpolicy.yaml')).read_text()) + + +def matches(selector, labels): + if any(labels.get(k) != v for k, v in selector.get('matchLabels', {}).items()): + return False + for expression in selector.get('matchExpressions', []): + key, op = expression['key'], expression['operator'] + if op == 'In' and labels.get(key) not in expression['values']: + return False + if op == 'DoesNotExist' and key in labels: + return False + return True + + +def allowed(component, labels, port, namespace='openserverless'): + # Ingress peers without namespaceSelector select only the policy namespace. + if namespace != 'openserverless': + return False + return any(any(p['port'] == port for p in rule['ports']) and + any(matches(peer['podSelector'], labels) for peer in rule['from']) + for rule in policy(component)['spec']['ingress']) + + +class MessagingSecurityTests(unittest.TestCase): + def tearDown(self): + config.clean() + + def test_existing_cluster_resume_installs_isolation_before_post_resume(self): + events = [] + owner = {'metadata': {'name': 'controller', 'uid': 'synthetic'}} + def configure(spec, **kwargs): + config.configure(spec) + with patch.object(main.operator_util, 'config_from_spec', side_effect=configure), \ + patch.object(main.kube, 'get', return_value=owner), \ + patch.object(zookeeper, 'create', side_effect=lambda obj: events.append(('zookeeper', obj))), \ + patch.object(kafka, 'create', side_effect=lambda obj: events.append(('kafka', obj))), \ + patch.object(main.operator_util, 'whisk_post_resume', side_effect=lambda name: events.append(('post', name))): + main.whisk_resume({'components': {'kafka': True, 'zookeeper': True}}, {}, 'controller') + self.assertEqual(events, [('zookeeper', owner), ('kafka', owner), ('post', 'controller')]) + + def test_resume_failure_propagates_and_never_reports_post_resume_success(self): + config.configure({'components': {'kafka': True, 'zookeeper': True}}) + with patch.object(main.operator_util, 'config_from_spec'), \ + patch.object(main.kube, 'get', return_value={'metadata': {'uid': 'synthetic'}}), \ + patch.object(zookeeper, 'create', side_effect=RuntimeError('policy denied')), \ + patch.object(kafka, 'create') as broker, \ + patch.object(main.operator_util, 'whisk_post_resume') as post: + with self.assertRaisesRegex(RuntimeError, 'policy denied'): + main.whisk_resume({}, {}, 'controller') + broker.assert_not_called() + post.assert_not_called() + + def test_configuration_update_reconciles_before_existing_patcher(self): + events = [] + config.configure({'components': {'kafka': True}}) + owner = {'metadata': {'uid': 'synthetic'}} + with patch.object(main.operator_util, 'config_from_spec'), \ + patch.object(main.kube, 'get', return_value=owner), \ + patch.object(kafka, 'create', side_effect=lambda obj: events.append(('kafka', obj))), \ + patch.object(zookeeper, 'create') as zoo, \ + patch.object(main.patcher, 'patch', side_effect=lambda *args: events.append(('patch', args))): + main.whisk_update({}, {}, 'openserverless', (), 'controller') + self.assertEqual(events, [('kafka', owner), ('patch', ((), {}, owner, 'controller'))]) + zoo.assert_not_called() + + def test_missing_owner_aborts_before_any_component_mutation(self): + config.configure({'components': {'kafka': True, 'zookeeper': True}}) + with patch.object(kafka, 'create') as broker, patch.object(zookeeper, 'create') as zoo: + for owner in [None, {}, {'metadata': {}}, {'metadata': {'name': 'controller'}}]: + with self.assertRaisesRegex(RuntimeError, 'Whisk owner'): + messaging_security.reconcile(owner) + broker.assert_not_called() + zoo.assert_not_called() + + def test_slim_resume_does_not_query_owner_or_create_messaging(self): + config.configure({'components': {'kafka': False, 'zookeeper': False}}) + with patch.object(main.operator_util, 'config_from_spec'), \ + patch.object(main.kube, 'get') as get, \ + patch.object(kafka, 'create') as broker, \ + patch.object(zookeeper, 'create') as zoo, \ + patch.object(main.operator_util, 'whisk_post_resume') as post: + main.whisk_resume({}, {}, 'controller') + messaging_security.reconcile(None) + get.assert_not_called() + broker.assert_not_called() + zoo.assert_not_called() + post.assert_called_once_with('controller') + + def test_kafka_rollout_failure_stops_update_processing(self): + config.configure({'components': {'kafka': True}}) + with patch.object(main.operator_util, 'config_from_spec'), \ + patch.object(main.kube, 'get', return_value={'metadata': {'uid': 'synthetic'}}), \ + patch.object(kafka, 'create', side_effect=RuntimeError('rollout timed out')), \ + patch.object(main.patcher, 'patch') as patcher: + with self.assertRaisesRegex(RuntimeError, 'rollout timed out'): + main.whisk_update({}, {}, 'openserverless', (), 'controller') + patcher.assert_not_called() + + def test_tenant_and_unrelated_pods_denied_on_all_ports(self): + for component, ports in [('kafka', [9092, 9071, 9888]), + ('zookeeper', [2181, 2888, 3888])]: + for labels in [{}, {'user-action-pod': 'true'}, + {'name': 'guest'}, {'name': 'openserverless-system-api'}]: + for port in ports: + with self.subTest(component=component, labels=labels, port=port): + self.assertFalse(allowed(component, labels, port)) + + def test_action_cannot_impersonate_trusted_peer_or_monitoring(self): + for component, ports in [('kafka', [9092, 9071, 9888]), + ('zookeeper', [2181, 2888, 3888])]: + for name in ['controller', 'invoker', 'scheduler', 'kafka', 'zookeeper']: + for marker in ['true', 'false', '']: + labels = {'name': name, 'user-action-pod': marker, + 'app.kubernetes.io/name': 'prometheus', + 'app.kubernetes.io/instance': 'openserverless-prometheus'} + for port in ports: + self.assertFalse(allowed(component, labels, port)) + + def test_same_labels_in_other_namespace_are_denied(self): + for component in ['kafka', 'zookeeper']: + for name in ['controller', 'invoker', 'kafka', 'zookeeper']: + self.assertFalse(allowed(component, {'name': name}, 9092, namespace='other')) + self.assertFalse(allowed(component, {'name': name}, 2181, namespace='other')) + + def test_control_plane_clients_preserve_required_connections(self): + for name in ['controller', 'invoker', 'scheduler', 'kafka']: + self.assertTrue(allowed('kafka', {'name': name}, 9092)) + for name in ['kafka', 'invoker', 'zookeeper']: + self.assertTrue(allowed('zookeeper', {'name': name}, 2181)) + for port in [2888, 3888]: + self.assertTrue(allowed('zookeeper', {'name': 'zookeeper'}, port)) + for name in ['controller', 'invoker', 'scheduler', 'kafka']: + self.assertFalse(allowed('zookeeper', {'name': name}, port)) + + def test_metrics_does_not_grant_broker_access(self): + labels = {'app.kubernetes.io/name': 'prometheus', + 'app.kubernetes.io/instance': 'openserverless-prometheus'} + for port in [9071, 9888]: + self.assertTrue(allowed('kafka', labels, port)) + self.assertFalse(allowed('kafka', labels, 9092)) + self.assertFalse(allowed('zookeeper', labels, 2181)) + + def test_all_policy_rules_have_scoped_peers_and_ports(self): + for component in ['kafka', 'zookeeper']: + obj = policy(component) + self.assertEqual(obj['spec']['podSelector']['matchLabels'], {'name': component}) + self.assertEqual(obj['spec']['policyTypes'], ['Ingress']) + for rule in obj['spec']['ingress']: + self.assertTrue(rule['ports']) + self.assertTrue(rule['from']) + for peer in rule['from']: + self.assertEqual(set(peer), {'podSelector'}) + self.assertTrue(peer['podSelector']) + + def test_policy_apply_failure_never_applies_workloads(self): + for component in ['kafka', 'zookeeper']: + p = policy(component) + workload = {'kind': 'StatefulSet', 'metadata': {'name': component}} + spec = {'apiVersion': 'v1', 'kind': 'List', 'items': [workload, p]} + with patch.object(messaging_security.kube, 'apply', side_effect=RuntimeError('denied')) as apply: + with self.assertRaisesRegex(RuntimeError, 'denied'): + messaging_security.apply(spec, component) + self.assertEqual(apply.call_count, 1) + self.assertEqual(apply.call_args.args[0]['items'], [p]) + + def test_missing_policy_fails_before_any_kubernetes_mutation(self): + for component in ['kafka', 'zookeeper']: + with patch.object(messaging_security.kube, 'apply') as apply: + with self.assertRaisesRegex(RuntimeError, 'Missing'): + messaging_security.apply({'items': []}, component) + apply.assert_not_called() + + def test_component_reconciliation_and_saved_deletion_spec_include_policy(self): + for component, module in [('kafka', kafka), ('zookeeper', zookeeper)]: + for owner in [None, {'apiVersion': 'openserverless.org/v1', 'kind': 'Whisk', + 'metadata': {'name': 'test', 'uid': 'synthetic'}}]: + config.clean() + config.put('openserverless.zookeeper.url', 'zookeeper-0.zookeeper:2181') + spec = {'apiVersion': 'v1', 'kind': 'List', + 'items': [{'kind': 'StatefulSet', 'metadata': {'name': component}}, + policy(component)]} + calls = [] + with patch.object(module.cfg_util, 'get_' + component + '_config_data', + return_value={'affinity': False, 'tolerations': False}), \ + patch.object(module.kus, 'patchTemplates', return_value=''), \ + patch.object(module.kus, 'kustom_list', return_value=spec), \ + patch.object(module.util, 'wait_for_pod_ready'), \ + patch.object(module, 'get_' + component + '_connect_data', return_value=component + ':2181'), \ + patch.object(messaging_security.kube, 'kubectl') as kubectl, \ + patch.object(messaging_security.kube, 'apply', + side_effect=lambda obj: calls.append(copy.deepcopy(obj)) or 'applied'): + self.assertEqual(module.create(owner), 'applied') + self.assertTrue(all(item['kind'] == 'NetworkPolicy' for item in calls[0]['items'])) + self.assertEqual(calls[1]['items'], spec['items']) + kubectl.assert_called_once_with('rollout', 'status', 'statefulset/' + component, '--timeout=300s') + if owner: + self.assertTrue(all(item['metadata'].get('ownerReferences') for item in calls[0]['items'])) + else: + saved = config.get('state.' + component + '.spec') + self.assertTrue(any(item['kind'] == 'NetworkPolicy' for item in saved['items'])) + with patch.object(module.security, 'delete', return_value='deleted') as delete: + self.assertEqual(module.delete(), 'deleted') + self.assertEqual(delete.call_args.args[0], saved) + + def test_legacy_sessions_are_closed_by_statefulset_rollout(self): + for component in ['kafka', 'zookeeper']: + path = next((ROOT / 'deploy' / component).glob('*sts.yaml')) + obj = next(item for item in yaml.safe_load_all(path.read_text()) + if item and item['kind'] == 'StatefulSet') + self.assertEqual(obj['spec']['template']['metadata']['annotations'] + ['openserverless.org/messaging-isolation'], 'v1') + + def test_deletion_keeps_policy_until_all_component_pods_are_gone(self): + for component in ['kafka', 'zookeeper']: + p = policy(component) + sts = {'kind': 'StatefulSet', 'metadata': {'name': component}} + events = [] + def kubectl(*args): + events.append(args[0]) + return 'pod/' + component + '-0' if args[0] == 'get' else '' + def delete(spec): + events.append(spec['items'][0]['kind']) + return 'deleted' + with patch.object(messaging_security.kube, 'kubectl', side_effect=kubectl), \ + patch.object(messaging_security.kube, 'delete', side_effect=delete): + messaging_security.delete({'items': [p, sts]}, component) + self.assertEqual(events, ['StatefulSet', 'get', 'wait', 'NetworkPolicy']) + + def test_delete_timeout_keeps_policy(self): + with patch.object(messaging_security.kube, 'kubectl', + side_effect=['pod/kafka-0', RuntimeError('still terminating')]), \ + patch.object(messaging_security.kube, 'delete', return_value='deleted') as delete: + with self.assertRaisesRegex(RuntimeError, 'still terminating'): + messaging_security.delete({'items': [policy('kafka'), {'kind': 'StatefulSet'}]}, 'kafka') + self.assertEqual(delete.call_count, 1) + + def test_legacy_saved_spec_without_policy_can_still_be_deleted(self): + with patch.object(messaging_security.kube, 'kubectl', return_value=''), \ + patch.object(messaging_security.kube, 'delete', return_value='deleted') as delete: + self.assertEqual(messaging_security.delete({'items': [{'kind': 'StatefulSet'}]}, 'kafka'), 'deleted') + self.assertEqual(delete.call_count, 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_operator_entrypoint.py b/tests/test_operator_entrypoint.py new file mode 100644 index 00000000..74a49299 --- /dev/null +++ b/tests/test_operator_entrypoint.py @@ -0,0 +1,49 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +"""Exercise the actual Kopf entrypoint, not a clean-process operator import.""" +import subprocess +import sys +import unittest + + +class OperatorEntrypointTest(unittest.TestCase): + def test_kopf_loading_preserves_third_party_redis_driver(self): + code = r'''from pathlib import Path +import shlex +from contextlib import nullcontext +from unittest.mock import patch +from kopf._cogs.helpers.loaders import preload +line = next(line for line in Path("run.sh").read_text().splitlines() if 'exec "$KOPF" run' in line) +args = shlex.split(line.split("exec ", 1)[1]) +modules = [args[i+1] for i, arg in enumerate(args[:-1]) if arg == "-m"] +paths = [arg for arg in args if arg.endswith(".py")] +preload(paths=paths, modules=modules) +from openserverless import redis as operator +import redis as driver +assert operator.redislib is driver and hasattr(driver, "Redis"), "Kopf shadowed the Redis driver" +with patch.object(operator.util, "get_redis_config_data", return_value={"redis_password":"synthetic"}), patch.object(operator, "service_endpoint", return_value=nullcontext(("127.0.0.1",6379))): + client=operator.admin_client() + assert client.connection_pool.connection_kwargs["host"]=="127.0.0.1" + client.close() +''' + result=subprocess.run([sys.executable,"-c",code],capture_output=True,text=True,timeout=30) + self.assertEqual(result.returncode,0,result.stderr) + + +if __name__ == "__main__":unittest.main() diff --git a/tests/test_redis_tenant_acl.py b/tests/test_redis_tenant_acl.py new file mode 100644 index 00000000..c521a680 --- /dev/null +++ b/tests/test_redis_tenant_acl.py @@ -0,0 +1,145 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import hashlib +import unittest +from unittest.mock import MagicMock, Mock, patch + +from openserverless import redis +from openserverless.redis_acl import tenant_rules +from openserverless.redis_client import RedisClient +from openserverless.user_config import UserConfig +from openserverless.user_metadata import UserMetadata +from openserverless.template import expand_template + + +class TenantPolicyTest(unittest.TestCase): + def test_key_channel_and_selector_grants_are_replaced(self): + for readonly in (True, False): + rules=tenant_rules('team:',readonly) + self.assertIn('-@all',rules);self.assertNotIn('+@all',rules) + for value in ['resetkeys','resetchannels','clearselectors','~team:*','&team:*']: + self.assertIn(value,rules) + for value in ['+eval','+evalsha','+eval_ro','+evalsha_ro','+script|load','+script|exists']: + self.assertIn(value,rules) + self.assertIn('-@admin',rules);self.assertIn('-@dangerous',rules) + if readonly:self.assertNotIn('+@write',rules) + + def test_invalid_prefixes_cannot_add_rules_or_wildcards(self): + for prefix in ['*','team:*','team?x:','team[ab]:','team: +@all','team:\n+@all','team\\:','']: + with self.assertRaises(ValueError):tenant_rules(prefix) + + def test_system_identities_cannot_be_managed_as_tenants(self): + for user in ['default','openserverless','invalid user','user\non','Uppercase','under_score','x'*64]: + with self.assertRaises(ValueError):redis._tenant_identity(user,'team:') + + def test_legacy_template_requires_explicit_internal_account(self): + data={'namespace':'team','prefix':'team:','mode':'create','redis_password':'synthetic','password':'synthetic'} + for mode in ['create','create_readonly','readonly','readandwrite']: + data['mode']=mode + self.assertNotIn('+@all',expand_template('redis_manage_user_tpl.txt',data)) + data['redis_system_account']=True + self.assertNotIn('+@all',expand_template('redis_manage_user_tpl.txt',data)) + data.update(namespace='openserverless',mode='create',redis_system_account=True) + self.assertIn('+@all',expand_template('redis_manage_user_tpl.txt',data)) + data['redis_system_account']=False + self.assertNotIn('+@all',expand_template('redis_manage_user_tpl.txt',data)) + + +class TenantLifecycleTest(unittest.TestCase): + def test_administrator_client_keeps_the_private_endpoint_for_its_lifetime(self): + endpoint = MagicMock() + endpoint.__enter__.return_value = ('127.0.0.1', 41238) + with patch.object(redis.util, 'get_redis_config_data', return_value={'redis_password': 'synthetic'}), \ + patch.object(redis, 'service_endpoint', return_value=endpoint): + client = redis.admin_client() + self.assertEqual(client.connection_pool.connection_kwargs['host'], '127.0.0.1') + self.assertEqual(client.connection_pool.connection_kwargs['port'], 41238) + endpoint.__exit__.assert_not_called() + client.close() + endpoint.__exit__.assert_called_once() + + def client(self): + client=MagicMock();client.__enter__.return_value=client + client.acl_getuser.return_value=None;client.config_get.return_value={'aclfile':''} + return client + + def test_creation_uses_hashes_and_resets_stale_grants(self): + client=self.client();password='synthetic with spaces > +@all' + with patch.object(redis,'admin_client',return_value=client): + self.assertTrue(redis.provision_user('team',password,'team:')) + args=client.execute_command.call_args_list[0].args + self.assertEqual(args[:5],('ACL','SETUSER','team','reset','on')) + self.assertIn('#'+hashlib.sha256(password.encode()).hexdigest(),args) + self.assertNotIn(password,args);self.assertNotIn('+@all',args) + client.acl_save.assert_not_called() + + def test_reconcile_preserves_existing_0_9_0_credentials(self): + client=self.client();client.acl_getuser.return_value={'passwords':['obsolete']} + with patch.object(redis,'admin_client',return_value=client):redis.provision_user('team','new synthetic','team:') + args=client.execute_command.call_args.args + self.assertIn('#obsolete',args) + self.assertIn('#'+hashlib.sha256(b'new synthetic').hexdigest(),args) + self.assertNotIn('CLIENT',args) + + def test_configured_acl_storage_is_saved(self): + client=self.client();client.config_get.return_value={'aclfile':'/synthetic/users.acl'} + with patch.object(redis,'admin_client',return_value=client):redis.provision_user('team','synthetic','team:') + client.acl_save.assert_called_once() + + def test_persistence_failure_closes_grant(self): + client=self.client();client.config_get.return_value={'aclfile':'/synthetic/users.acl'} + client.acl_save.side_effect=redis.redislib.ResponseError('synthetic failure') + with patch.object(redis,'admin_client',return_value=client): + with self.assertRaisesRegex(RuntimeError,'persist'):redis.provision_user('team','synthetic','team:') + client.acl_deluser.assert_called_once_with('team') + + def test_quota_transitions_preserve_credentials_and_never_restore_all(self): + client=self.client();client.acl_getuser.return_value={'passwords':['synthetic hash']} + with patch.object(redis,'admin_client',return_value=client): + for readonly in (True,False):redis.set_user_readonly('team','team:',readonly) + for call in client.execute_command.call_args_list: + self.assertEqual(call.args[:3],('ACL','SETUSER','team')) + self.assertNotIn('reset',call.args);self.assertNotIn('+@all',call.args) + self.assertFalse(any(str(arg).startswith('#') for arg in call.args[3:])) + + def test_both_quota_entrypoints_share_enforcement(self): + obj=RedisClient.__new__(RedisClient) + with patch.object(redis,'set_user_readonly',return_value=True) as apply: + self.assertTrue(obj.set_prefix_readonly('team','team:')) + self.assertTrue(obj.set_prefix_all('team','team:')) + self.assertEqual(apply.call_args_list[0].args,('team','team:',True)) + self.assertEqual(apply.call_args_list[1].args,('team','team:',False)) + + def test_create_and_reconcile_use_policy_and_preserve_metadata(self): + for readonly in (False,True): + cfg=UserConfig({'namespace':'team','password':'synthetic','redis':{'password':'synthetic','prefix':'team'}}) + metadata=UserMetadata(cfg) + with patch.object(redis,'wait_for_redis_ready'),patch.object(redis,'provision_user',return_value=True) as provision,patch.object(redis,'_add_redis_user_metadata'): + self.assertTrue(redis.create_db_user(cfg,metadata,readonly)) + provision.assert_called_once_with('team','synthetic','team:',readonly) + + def test_missing_identity_is_not_created_by_a_quota_transition(self): + client=self.client() + with patch.object(redis,'admin_client',return_value=client): + with self.assertRaisesRegex(RuntimeError,'does not exist'):redis.set_user_readonly('team','team:',True) + client.execute_command.assert_not_called() + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/tests/test_s3_anonymous_access.py b/tests/test_s3_anonymous_access.py new file mode 100644 index 00000000..54725763 --- /dev/null +++ b/tests/test_s3_anonymous_access.py @@ -0,0 +1,196 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import json +import unittest +from unittest.mock import Mock, call, patch + +from openserverless import seaweedfs_deploy + + +class AnonymousS3AccessTest(unittest.TestCase): + def test_grants_are_bucket_scoped_and_reject_invalid_names(self): + client = seaweedfs_deploy.SeaweedfsClient.__new__(seaweedfs_deploy.SeaweedfsClient) + client._exec_weed_command = Mock(return_value=True) + self.assertTrue(client.add_anonymous_access(["beta-web", "alpha-web"])) + client._exec_weed_command.assert_called_once_with( + "s3.configure -user=anonymous -actions=Read -buckets=alpha-web,beta-web -apply") + for buckets in ([], ["bad,bucket"], ["bad\n-command"]): + client._exec_weed_command.reset_mock() + with self.assertRaises(ValueError): + client.add_anonymous_access(buckets) + client._exec_weed_command.assert_not_called() + + def test_bucket_revocation_does_not_remove_other_public_grants(self): + client = seaweedfs_deploy.SeaweedfsClient.__new__(seaweedfs_deploy.SeaweedfsClient) + client._exec_weed_command = Mock(return_value=True) + self.assertTrue(client.remove_anonymous_access("alpha-web")) + client._exec_weed_command.assert_called_once_with( + "s3.configure -user=anonymous -actions=Read -buckets=alpha-web -delete -apply") + + @patch.object(seaweedfs_deploy, "_configured_storage_buckets", return_value=({"alpha-web"}, {"alpha-data"})) + def test_failed_legacy_revoke_never_adds_grants(self, buckets): + client = Mock() + client.delete_user.return_value = False + with self.assertRaises(RuntimeError): + seaweedfs_deploy.reconcile_anonymous_web_access(client) + client.add_anonymous_access.assert_not_called() + + @patch.object(seaweedfs_deploy, "SeaweedfsClient") + def test_failed_revoke_preserves_web_bucket_and_identity_until_retry(self, factory): + config = Mock() + config.get.side_effect = { + "namespace": "alpha", + "object-storage.data.enabled": False, + "object-storage.route.enabled": True, + "object-storage.route.bucket": "alpha-web", + }.get + client = factory.return_value + for failure in (False, RuntimeError("S3 unavailable")): + with self.subTest(failure=failure): + client.reset_mock() + client.remove_anonymous_access.side_effect = None + client.remove_anonymous_access.return_value = False + if isinstance(failure, Exception): + client.remove_anonymous_access.side_effect = failure + with self.assertRaises(RuntimeError): + seaweedfs_deploy.delete_ow_storage(config) + client.force_bucket_remove.assert_not_called() + client.delete_user.assert_not_called() + + client.reset_mock() + client.remove_anonymous_access.side_effect = None + client.remove_anonymous_access.return_value = True + client.delete_user.return_value = True + self.assertTrue(seaweedfs_deploy.delete_ow_storage(config)) + self.assertEqual(client.mock_calls, [ + call.remove_anonymous_access("alpha-web"), + call.force_bucket_remove("alpha-web"), + call.delete_user("alpha"), + ]) + + @patch.object(seaweedfs_deploy, "SeaweedfsClient") + def test_revoke_precedes_data_deletion_and_shared_bucket_is_deleted_once(self, factory): + config = Mock() + values = { + "namespace": "legacy", + "object-storage.data.enabled": True, + "object-storage.data.bucket": "shared", + "object-storage.route.enabled": True, + "object-storage.route.bucket": "shared", + } + config.get.side_effect = values.get + client = factory.return_value + client.remove_anonymous_access.return_value = False + with self.assertRaises(RuntimeError): + seaweedfs_deploy.delete_ow_storage(config) + client.force_bucket_remove.assert_not_called() + client.delete_user.assert_not_called() + + client.reset_mock() + client.remove_anonymous_access.return_value = True + client.delete_user.return_value = True + self.assertTrue(seaweedfs_deploy.delete_ow_storage(config)) + self.assertEqual(client.mock_calls, [ + call.remove_anonymous_access("shared"), + call.force_bucket_remove("shared"), + call.delete_user("legacy"), + ]) + + @patch.object(seaweedfs_deploy, "SeaweedfsClient") + def test_failed_revoke_preserves_distinct_private_and_public_buckets(self, factory): + config = Mock() + config.get.side_effect = { + "namespace": "alpha", + "object-storage.data.enabled": True, + "object-storage.data.bucket": "alpha-data", + "object-storage.route.enabled": True, + "object-storage.route.bucket": "alpha-web", + }.get + client = factory.return_value + client.remove_anonymous_access.return_value = False + with self.assertRaises(RuntimeError): + seaweedfs_deploy.delete_ow_storage(config) + client.force_bucket_remove.assert_not_called() + client.delete_user.assert_not_called() + + @patch.object(seaweedfs_deploy.kube, "kubectl") + def test_reconcile_legacy_grant_preserves_only_public_buckets(self, kubectl): + kubectl.return_value = json.dumps({"items": [ + {"spec": {"object-storage": {"data": {"enabled": True, "bucket": "alpha-data"}, + "route": {"enabled": True, "bucket": "alpha-web"}}}}, + {"spec": {"object-storage": {"data": {"enabled": True, "bucket": "beta-data"}, + "route": {"enabled": False, "bucket": "beta-web"}}}}, + ]}) + client = Mock() + client.delete_user.return_value = True + client.add_anonymous_access.return_value = True + seaweedfs_deploy.reconcile_anonymous_web_access(client) + client.delete_user.assert_called_once_with("anonymous") + client.add_anonymous_access.assert_called_once_with({"openserverless-web", "alpha-web"}) + + @patch.object(seaweedfs_deploy.kube, "kubectl") + def test_rejects_bucket_used_for_both_data_and_web(self, kubectl): + kubectl.return_value = json.dumps({"items": [ + {"spec": {"object-storage": {"data": {"enabled": True, "bucket": "shared"}, + "route": {"enabled": True, "bucket": "shared"}}}}, + ]}) + client = Mock() + client.delete_user.return_value = True + client.add_anonymous_access.return_value = True + with self.assertRaisesRegex(ValueError, "overlap"): + seaweedfs_deploy.reconcile_anonymous_web_access(client) + client.delete_user.assert_called_once_with("anonymous") + client.add_anonymous_access.assert_called_once_with({"openserverless-web"}) + + @patch.object(seaweedfs_deploy.SeaweedfsClient, "__init__", return_value=None) + @patch.object(seaweedfs_deploy.kube, "kubectl") + def test_new_route_cannot_expose_another_tenants_private_bucket(self, kubectl, client_init): + kubectl.return_value = json.dumps({"items": [ + {"spec": {"object-storage": {"data": {"enabled": True, "bucket": "alpha-data"}}}}, + ]}) + config = Mock() + config.get.side_effect = lambda key: { + "namespace": "beta", "object-storage.password": "unused", + "object-storage.route.enabled": True, + "object-storage.route.bucket": "alpha-data", + "object-storage.data.enabled": False, + }.get(key) + with self.assertRaisesRegex(ValueError, "overlap"): + seaweedfs_deploy.create_ow_storage({}, config, Mock()) + client_init.assert_called_once_with() + + @patch.object(seaweedfs_deploy.SeaweedfsClient, "__init__", return_value=None) + @patch.object(seaweedfs_deploy.kube, "kubectl") + def test_new_data_cannot_reuse_public_bucket(self, kubectl, client_init): + kubectl.return_value = json.dumps({"items": [ + {"spec": {"object-storage": {"route": {"enabled": True, "bucket": "alpha-web"}}}}, + ]}) + config = Mock() + config.get.side_effect = lambda key: { + "namespace": "beta", "object-storage.password": "unused", + "object-storage.route.enabled": False, + "object-storage.data.enabled": True, + "object-storage.data.bucket": "alpha-web", + }.get(key) + with self.assertRaisesRegex(ValueError, "overlap"): + seaweedfs_deploy.create_ow_storage({}, config, Mock()) + client_init.assert_called_once_with() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/whisk-full.yaml b/tests/whisk-full.yaml index 746af2cf..28bb7a68 100644 --- a/tests/whisk-full.yaml +++ b/tests/whisk-full.yaml @@ -42,8 +42,8 @@ spec: cron: true # activate tls support tls: false - # minio enabled or not - minio: true + # seaweedfs enabled or not + seaweedfs: true # zookeeper enabled or not zookeeper: true # monitoring enabled or not @@ -90,14 +90,7 @@ spec: user: controller_admin password: s0meP@ass2 mongodb: - host: mongodb volume-size: 10 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 exposed: true kafka: host: kafka @@ -116,7 +109,7 @@ spec: tag: "0.3.0-morpheus.22122609" scheduler: schedule: "* * * * *" - minio: + seaweedfs: ingress: s3-enabled: true console-enabled: true @@ -124,8 +117,8 @@ spec: console-hostname: auto volume-size: 2 openserverless: - root-user: minioadmin - root-password: minioadmin + user: openserverless + password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG zookeeper: host: zookeper data-volume-size: 5 diff --git a/tests/whisk.yaml b/tests/whisk.yaml index c918840c..56d64c9e 100644 --- a/tests/whisk.yaml +++ b/tests/whisk.yaml @@ -42,8 +42,6 @@ spec: cron: true # tls tls: false - # minio enabled or not - minio: true # postgres enabled or not postgres: true # etcd enabled or not @@ -53,7 +51,7 @@ spec: # registry enabled or not registry: true # seaweedfs enabled or not - seaweedfs: false + seaweedfs: true openwhisk: namespaces: whisk-system: 789c46b1-71f6-4ed5-8c54-816aa4f8c502:abczO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP @@ -98,24 +96,7 @@ spec: prefix: openserverless password: s0meP@ass3 mongodb: - host: mongodb volume-size: 5 - admin: - user: whisk_admin - password: 0therPa55 - openserverless: - user: openserverless - password: s0meP@ass3 - exposedExternally: False - useOperator: False - minio: - volume-size: 2 - admin: - user: minioadmin - password: minioadmin - openserverless: - user: openserverless - password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG postgres: volume-size: 5 replicas: 2 diff --git a/whisk-system.sh b/whisk-system.sh index 8214d646..e5345c88 100755 --- a/whisk-system.sh +++ b/whisk-system.sh @@ -36,7 +36,7 @@ zip -r ${HOME}/deploy/whisk-system/secrets.zip * cd ${HOME}/actions/content mkdir -p ${HOME}/actions/content/common -cp ${HOME}/actions/common/minio_util.py ${HOME}/actions/content/common +cp ${HOME}/actions/common/s3_util.py ${HOME}/actions/content/common rm -f ${HOME}/deploy/whisk-system/content.zip zip -r ${HOME}/deploy/whisk-system/content.zip * @@ -56,13 +56,13 @@ cd ${HOME}/actions/devel/psql rm -f ${HOME}/deploy/whisk-system/psql.zip zip -r ${HOME}/deploy/whisk-system/psql.zip * -mkdir -p ${HOME}/actions/devel/minio/openserverless -mkdir -p ${HOME}/actions/devel/minio/common -cp ${HOME}/openserverless/config.py ${HOME}/openserverless/couchdb_util.py ${HOME}/actions/devel/minio/openserverless -cp ${HOME}/actions/common/*.py ${HOME}/actions/devel/minio/common -cd ${HOME}/actions/devel/minio -rm -f ${HOME}/deploy/whisk-system/minio.zip -zip -r ${HOME}/deploy/whisk-system/minio.zip * +mkdir -p ${HOME}/actions/devel/s3/openserverless +mkdir -p ${HOME}/actions/devel/s3/common +cp ${HOME}/openserverless/config.py ${HOME}/openserverless/couchdb_util.py ${HOME}/actions/devel/s3/openserverless +cp ${HOME}/actions/common/*.py ${HOME}/actions/devel/s3/common +cd ${HOME}/actions/devel/s3 +rm -f ${HOME}/deploy/whisk-system/s3.zip +zip -r ${HOME}/deploy/whisk-system/s3.zip * mkdir -p ${HOME}/actions/devel/ferretdb/openserverless mkdir -p ${HOME}/actions/devel/ferretdb/common