From 1de4197f5dcf6eecca6ce819ca9f838bac1ea51e Mon Sep 17 00:00:00 2001 From: Anton Chub Date: Fri, 16 Aug 2024 14:33:13 +0200 Subject: [PATCH 1/5] #minor Add option to request only once per batch --- README.md | 3 ++- src/resources.py | 21 +++++++++++++++------ src/sidecar.py | 15 +++++++++++++-- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e97b659b..6ec3d171 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ All are identical multi-arch images built for `amd64`, `arm64`, `arm/v7`, `ppc64 - Values can also be base64 encoded URLs that download binary data e.g. executables - The key in the `ConfigMap`/`Secret` must end with "`.url`" ([see](https://github.com/kiwigrid/k8s-sidecar/blob/master/test/resources/resources.yaml#L84)) -# Usage +# Usage Example for a simple deployment can be found in [`example.yaml`](./examples/example.yaml). Depending on the cluster setup you have to grant yourself admin rights first: ```shell @@ -84,6 +84,7 @@ If the filename ends with `.url` suffix, the content will be processed as a URL | `REQ_USERNAME` | Username to use for basic authentication for requests to `REQ_URL` and for `*.url` triggered requests | false | - | string | | `REQ_PASSWORD` | Password to use for basic authentication for requests to `REQ_URL` and for `*.url` triggered requests | false | - | string | | `REQ_BASIC_AUTH_ENCODING` | Which encoding to use for username and password as [by default it's undefined](https://datatracker.ietf.org/doc/html/rfc7617) (e.g. `utf-8`). | false | `latin1` | string | +| `REQ_ONCE_PER_BATCH` | Send request to `REQ_URL` only once each `WATCH_SERVER_TIMEOUT`. By default request is made after each change. Applicable only to `METHOD=WATCH`. | `SCRIPT` | Absolute path to a script to execute after a configmap got reloaded. It runs before calls to `REQ_URI`. If the file is not executable it will be passed to `sh`. Otherwise it's executed as is. [Shebangs](https://en.wikipedia.org/wiki/Shebang_(Unix)) known to work are `#!/bin/sh` and `#!/usr/bin/env python` | false | - | string | | `ERROR_THROTTLE_SLEEP` | How many seconds to wait before watching resources again when an error occurs | false | `5` | integer | | `SKIP_TLS_VERIFY` | Set to `true` to skip tls verification for kube api calls | false | - | boolean | diff --git a/src/resources.py b/src/resources.py index ca3533f1..70d1e2f8 100755 --- a/src/resources.py +++ b/src/resources.py @@ -305,7 +305,7 @@ def _update_file(data_key, data_content, dest_folder, metadata, resource, def _watch_resource_iterator(label, label_value, target_folder, request_url, request_method, request_payload, namespace, folder_annotation, resource, unique_filenames, script, enable_5xx, - ignore_already_processed): + ignore_already_processed, request_once_per_batch): v1 = client.CoreV1Api() # Filter resources based on label and value or just label label_selector = f"{label}={label_value}" if label_value else label @@ -322,6 +322,9 @@ def _watch_resource_iterator(label, label_value, target_folder, request_url, req stream = watch.Watch().stream(getattr(v1, _list_namespace[namespace][resource]), **additional_args) + # Used if request_once_per_batch is enabled + any_files_changed = False + # Process events for event in stream: item = event['object'] @@ -355,12 +358,18 @@ def _watch_resource_iterator(label, label_value, target_folder, request_url, req else: files_changed |= _process_secret(dest_folder, item, resource, unique_filenames, enable_5xx, item_removed) + any_files_changed |= files_changed + if script and files_changed: execute(script) - if request_url and files_changed: + if request_url and files_changed and not request_once_per_batch: request(request_url, request_method, enable_5xx, request_payload) + if request_url and any_files_changed and request_once_per_batch: + logger.debug(f"Starting batch request") + request(request_url, request_method, enable_5xx, request_payload) + def _watch_resource_loop(mode, *args): while True: @@ -389,11 +398,11 @@ def _watch_resource_loop(mode, *args): def watch_for_changes(mode, label, label_value, target_folder, request_url, request_method, request_payload, current_namespace, folder_annotation, resources, unique_filenames, script, enable_5xx, - ignore_already_processed): + ignore_already_processed, request_once_per_batch): processes = _start_watcher_processes(current_namespace, folder_annotation, label, label_value, request_method, mode, request_payload, resources, target_folder, unique_filenames, script, request_url, enable_5xx, - ignore_already_processed) + ignore_already_processed, request_once_per_batch) while True: died = False @@ -413,14 +422,14 @@ def watch_for_changes(mode, label, label_value, target_folder, request_url, requ def _start_watcher_processes(namespace, folder_annotation, label, label_value, request_method, mode, request_payload, resources, target_folder, unique_filenames, script, request_url, - enable_5xx, ignore_already_processed): + enable_5xx, ignore_already_processed, request_once_per_batch): processes = [] for resource in resources: for ns in namespace.split(','): proc = Process(target=_watch_resource_loop, args=(mode, label, label_value, target_folder, request_url, request_method, request_payload, ns, folder_annotation, resource, unique_filenames, script, enable_5xx, - ignore_already_processed) + ignore_already_processed, request_once_per_batch) ) proc.daemon = True proc.start() diff --git a/src/sidecar.py b/src/sidecar.py index 6312b958..9ee2d654 100644 --- a/src/sidecar.py +++ b/src/sidecar.py @@ -24,6 +24,7 @@ REQ_PAYLOAD = "REQ_PAYLOAD" REQ_URL = "REQ_URL" REQ_METHOD = "REQ_METHOD" +REQ_ONCE_PER_BATCH = "REQ_ONCE_PER_BATCH" SCRIPT = "SCRIPT" ENABLE_5XX = "ENABLE_5XX" IGNORE_ALREADY_PROCESSED = "IGNORE_ALREADY_PROCESSED" @@ -72,10 +73,19 @@ def main(): request_method = os.getenv(REQ_METHOD) request_url = os.getenv(REQ_URL) - + request_payload = os.getenv(REQ_PAYLOAD) if request_payload: request_payload = prepare_payload(os.getenv(REQ_PAYLOAD)) + + request_once_per_batch = os.getenv(REQ_ONCE_PER_BATCH) + if request_once_per_batch is not None and request_once_per_batch.lower() == "true": + logger.info(f"Request once per batch will be enabled.") + request_once_per_batch = True + else: + logger.info(f"Request once per batch will not be enabled.") + request_once_per_batch = False + script = os.getenv(SCRIPT) _initialize_kubeclient_configuration() @@ -131,7 +141,8 @@ def main(): else: watch_for_changes(method, label, label_value, target_folder, request_url, request_method, request_payload, namespace, folder_annotation, resources, unique_filenames, script, enable_5xx, - ignore_already_processed) + ignore_already_processed, + request_once_per_batch) def _initialize_kubeclient_configuration(): From f6dc6126b4e5b86045fb8a1ee489637a36c7c4d5 Mon Sep 17 00:00:00 2001 From: Anton Chub Date: Tue, 10 Sep 2024 13:36:12 +0200 Subject: [PATCH 2/5] Creating test case for batch reload --- .github/workflows/build_and_test.yaml | 20 +++++++++++---- README.md | 2 +- test/resources/sidecar.yaml | 35 ++++++++++++++++++++++++--- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 993a8ffc..8b6f6da3 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -53,7 +53,7 @@ jobs: - maj_min: v1.27 digest: sha256:3700c811144e24a6c6181065265f69b9bf0b437c45741017182d7c82b908918f - maj_min: v1.28 - digest: sha256:b7e1cf6b2b729f604133c667a6be8aab6f4dde5bb042c1891ae248d9154f665b + digest: sha256:b7e1cf6b2b729f604133c667a6be8aab6f4dde5bb042c1891ae248d9154f665b - maj_min: v1.29 digest: sha256:a0cc28af37cf39b019e2b448c54d1a3f789de32536cb5a5db61a49623e527144 name: "Test on k8s ${{ matrix.k8s.maj_min }}" @@ -97,6 +97,7 @@ jobs: wait_for_pod_ready "sidecar-pythonscript" wait_for_pod_ready "sidecar-pythonscript-logfile" wait_for_pod_ready "sidecar-logtofile-pythonscript" + wait_for_pod_ready "sidecar-batch-reload" wait_for_pod_ready "dummy-server-pod" - name: Install Configmaps and Secrets @@ -109,7 +110,7 @@ jobs: sleep 20 echo "Installing resources..." kubectl apply -f "test/resources/resources.yaml" - pods=("sidecar" "sidecar-basicauth-args" "sidecar-5xx" "sidecar-pythonscript" "sidecar-pythonscript-logfile") + pods=("sidecar" "sidecar-basicauth-args" "sidecar-5xx" "sidecar-pythonscript" "sidecar-pythonscript-logfile" "sidecar-batch-reload") resources=("sample-configmap" "sample-secret-binary" "absolute-configmap" "relative-configmap" "change-dir-configmap" "similar-configmap-secret" "url-configmap-500" "url-configmap-basic-auth" "sample-configmap") for p in ${pods[*]}; do for r in ${resources[*]}; do @@ -127,6 +128,10 @@ jobs: kubectl logs sidecar-pythonscript > /tmp/logs/sidecar-pythonscript.log kubectl logs sidecar-pythonscript-logfile > /tmp/logs/sidecar-pythonscript-logfile.log kubectl logs dummy-server-pod > /tmp/logs/dummy-server.log + + # Sleep more to pass WATCH_SERVER_TIMEOUT seconds for batch-reload to occur + sleep 60 + kubectl logs sidecar-batch-reload > /tmp/logs/sidecar-batch-reload.log - name: Upload artifacts (pod logs) uses: actions/upload-artifact@v3 with: @@ -151,10 +156,10 @@ jobs: kubectl cp sidecar:/tmp/secured.txt /tmp/sidecar/secured.txt kubectl cp sidecar:/tmp/similar-configmap.txt /tmp/sidecar/similar-configmap.txt kubectl cp sidecar:/tmp/similar-secret.txt /tmp/sidecar/similar-secret.txt - + echo "Downloading resource files from sidecar-basicauth-args pod" kubectl cp sidecar-basicauth-args:/tmp/secured.txt /tmp/sidecar-basicauth-args/secured.txt - + echo "Downloading resource files from sidecar-5xx..." kubectl cp sidecar-5xx:/tmp-5xx/hello.world /tmp/sidecar-5xx/hello.world kubectl cp sidecar-5xx:/tmp-5xx/cm-kubelogo.png /tmp/sidecar-5xx/cm-kubelogo.png @@ -246,4 +251,9 @@ jobs: kubectl exec sidecar -- sh -c "! test -e /tmp/relative/relative.txt" && kubectl exec sidecar -- sh -c "test -e /tmp/relative/change-relative.txt" && kubectl exec sidecar -- sh -c "! test -e /tmp/orig-dir/change-dir.txt" && kubectl exec sidecar -- sh -c "test -e /tmp/new-dir/change-dir.txt" && kubectl exec sidecar -- sh -c "! test -e /tmp/similar-configmap.txt" && kubectl exec sidecar -- sh -c "test -e /tmp/change-similar-configmap.txt" && - kubectl exec sidecar -- sh -c "! test -e /tmp/similar-secret.txt" && kubectl exec sidecar -- sh -c "test -e /tmp/change-similar-secret.txt" \ No newline at end of file + kubectl exec sidecar -- sh -c "! test -e /tmp/similar-secret.txt" && kubectl exec sidecar -- sh -c "test -e /tmp/change-similar-secret.txt" + # - name: Verify sidecar-batch-reload logs after initial sync + # run: | + # test $(cat /tmp/logs/sidecar-sidecar-batch-reload.log | grep "Hello from python script!" | wc -l) = "9" && + # kubectl exec sidecar-logtofile-pythonscript -- sh -c "test -e /opt/logs/sidecar.log" && + # test $(kubectl exec sidecar-logtofile-pythonscript -- sh -c 'cat /opt/logs/sidecar.log | grep "Hello from python script!" | wc -l') = "16" diff --git a/README.md b/README.md index 6ec3d171..604340b6 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ If the filename ends with `.url` suffix, the content will be processed as a URL | `REQ_USERNAME` | Username to use for basic authentication for requests to `REQ_URL` and for `*.url` triggered requests | false | - | string | | `REQ_PASSWORD` | Password to use for basic authentication for requests to `REQ_URL` and for `*.url` triggered requests | false | - | string | | `REQ_BASIC_AUTH_ENCODING` | Which encoding to use for username and password as [by default it's undefined](https://datatracker.ietf.org/doc/html/rfc7617) (e.g. `utf-8`). | false | `latin1` | string | -| `REQ_ONCE_PER_BATCH` | Send request to `REQ_URL` only once each `WATCH_SERVER_TIMEOUT`. By default request is made after each change. Applicable only to `METHOD=WATCH`. +| `REQ_ONCE_PER_BATCH` | Send request to `REQ_URL` only once each `WATCH_SERVER_TIMEOUT`. By default request is made after each change. Applicable only to `METHOD=WATCH`. | false | - | boolean | | `SCRIPT` | Absolute path to a script to execute after a configmap got reloaded. It runs before calls to `REQ_URI`. If the file is not executable it will be passed to `sh`. Otherwise it's executed as is. [Shebangs](https://en.wikipedia.org/wiki/Shebang_(Unix)) known to work are `#!/bin/sh` and `#!/usr/bin/env python` | false | - | string | | `ERROR_THROTTLE_SLEEP` | How many seconds to wait before watching resources again when an error occurs | false | `5` | integer | | `SKIP_TLS_VERIFY` | Set to `true` to skip tls verification for kube api calls | false | - | boolean | diff --git a/test/resources/sidecar.yaml b/test/resources/sidecar.yaml index 40ca9c44..3bfac1b0 100644 --- a/test/resources/sidecar.yaml +++ b/test/resources/sidecar.yaml @@ -168,7 +168,7 @@ data: root: level: DEBUG handlers: [console] - + handlers: console: class: logging.StreamHandler @@ -180,7 +180,7 @@ data: (): logger.JsonFormatter format: '%(levelname)s %(message)s' rename_fields: { - "message": "msg", + "message": "msg", "levelname": "level" } --- @@ -388,4 +388,33 @@ metadata: type: Opaque stringData: username: "user1" - password: "abcdefghijklmnopqrstuvwxyz" \ No newline at end of file + password: "abcdefghijklmnopqrstuvwxyz" + +--- +apiVersion: v1 +kind: Pod +metadata: + name: sidecar-batch-reload + namespace: default +spec: + serviceAccountName: sample-acc + containers: + - name: sidecar + image: kiwigrid/k8s-sidecar:testing + volumeMounts: + - name: shared-volume + mountPath: /tmp/ + env: + - name: LABEL + value: "findme" + - name: FOLDER + value: /tmp/ + - name: RESOURCE + value: both + - name: REQ_ONCE_PER_BATCH + value: "true" + - name: LOG_LEVEL + value: "DEBUG" + volumes: + - name: shared-volume + emptyDir: {} From 261966a95366f424556c3ec053e667bc68750a95 Mon Sep 17 00:00:00 2001 From: Anton Chub Date: Tue, 10 Sep 2024 14:04:49 +0200 Subject: [PATCH 3/5] Added support to accept post req for dummy server --- test/resources/sidecar.yaml | 2 ++ test/server/server.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/resources/sidecar.yaml b/test/resources/sidecar.yaml index 3bfac1b0..4032cf9b 100644 --- a/test/resources/sidecar.yaml +++ b/test/resources/sidecar.yaml @@ -411,6 +411,8 @@ spec: value: /tmp/ - name: RESOURCE value: both + - name: REQ_URL + value: http://dummy-server/reload - name: REQ_ONCE_PER_BATCH value: "true" - name: LOG_LEVEL diff --git a/test/server/server.py b/test/server/server.py index 23cf915c..19794b93 100644 --- a/test/server/server.py +++ b/test/server/server.py @@ -45,4 +45,8 @@ async def read_secure_data(auth: HTTPBasicCredentials = Depends(basic_auth_schem detail=f"Incorrect user (${auth.username}) or password (${auth.password})", headers={"WWW-Authenticate": "Basic"}, ) - return 'allowed' \ No newline at end of file + return 'allowed' + +@app.post("/reload", status_code=201) +async def read_item(): + return 201 From 50c5e718785fb424cc2036716123a7aefe50a6b5 Mon Sep 17 00:00:00 2001 From: Anton Chub Date: Tue, 10 Sep 2024 14:21:28 +0200 Subject: [PATCH 4/5] Fix req parameters --- test/resources/sidecar.yaml | 4 +++- test/server/server.py | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/test/resources/sidecar.yaml b/test/resources/sidecar.yaml index 4032cf9b..fea448bd 100644 --- a/test/resources/sidecar.yaml +++ b/test/resources/sidecar.yaml @@ -412,7 +412,9 @@ spec: - name: RESOURCE value: both - name: REQ_URL - value: http://dummy-server/reload + value: http://dummy-server/ + - name: REQ_METHOD + value: GET - name: REQ_ONCE_PER_BATCH value: "true" - name: LOG_LEVEL diff --git a/test/server/server.py b/test/server/server.py index 19794b93..6d11f39f 100644 --- a/test/server/server.py +++ b/test/server/server.py @@ -47,6 +47,3 @@ async def read_secure_data(auth: HTTPBasicCredentials = Depends(basic_auth_schem ) return 'allowed' -@app.post("/reload", status_code=201) -async def read_item(): - return 201 From be73d8afedcbeda03ee619a030d0374c8dc76e7f Mon Sep 17 00:00:00 2001 From: Anton Chub Date: Tue, 10 Sep 2024 14:40:50 +0200 Subject: [PATCH 5/5] Added stage to test batch reload --- .github/workflows/build_and_test.yaml | 19 +++++++++---------- test/resources/sidecar.yaml | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 8b6f6da3..e13f521b 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -97,7 +97,7 @@ jobs: wait_for_pod_ready "sidecar-pythonscript" wait_for_pod_ready "sidecar-pythonscript-logfile" wait_for_pod_ready "sidecar-logtofile-pythonscript" - wait_for_pod_ready "sidecar-batch-reload" + wait_for_pod_ready "sidecar-req-once-per-batch" wait_for_pod_ready "dummy-server-pod" - name: Install Configmaps and Secrets @@ -110,7 +110,7 @@ jobs: sleep 20 echo "Installing resources..." kubectl apply -f "test/resources/resources.yaml" - pods=("sidecar" "sidecar-basicauth-args" "sidecar-5xx" "sidecar-pythonscript" "sidecar-pythonscript-logfile" "sidecar-batch-reload") + pods=("sidecar" "sidecar-basicauth-args" "sidecar-5xx" "sidecar-pythonscript" "sidecar-pythonscript-logfile" "sidecar-req-once-per-batch") resources=("sample-configmap" "sample-secret-binary" "absolute-configmap" "relative-configmap" "change-dir-configmap" "similar-configmap-secret" "url-configmap-500" "url-configmap-basic-auth" "sample-configmap") for p in ${pods[*]}; do for r in ${resources[*]}; do @@ -129,9 +129,9 @@ jobs: kubectl logs sidecar-pythonscript-logfile > /tmp/logs/sidecar-pythonscript-logfile.log kubectl logs dummy-server-pod > /tmp/logs/dummy-server.log - # Sleep more to pass WATCH_SERVER_TIMEOUT seconds for batch-reload to occur - sleep 60 - kubectl logs sidecar-batch-reload > /tmp/logs/sidecar-batch-reload.log + # Sleep more to pass WATCH_SERVER_TIMEOUT seconds for request once per batch to occur + sleep 40 + kubectl logs sidecar-req-once-per-batch > /tmp/logs/sidecar-req-once-per-batch.log - name: Upload artifacts (pod logs) uses: actions/upload-artifact@v3 with: @@ -252,8 +252,7 @@ jobs: kubectl exec sidecar -- sh -c "! test -e /tmp/orig-dir/change-dir.txt" && kubectl exec sidecar -- sh -c "test -e /tmp/new-dir/change-dir.txt" && kubectl exec sidecar -- sh -c "! test -e /tmp/similar-configmap.txt" && kubectl exec sidecar -- sh -c "test -e /tmp/change-similar-configmap.txt" && kubectl exec sidecar -- sh -c "! test -e /tmp/similar-secret.txt" && kubectl exec sidecar -- sh -c "test -e /tmp/change-similar-secret.txt" - # - name: Verify sidecar-batch-reload logs after initial sync - # run: | - # test $(cat /tmp/logs/sidecar-sidecar-batch-reload.log | grep "Hello from python script!" | wc -l) = "9" && - # kubectl exec sidecar-logtofile-pythonscript -- sh -c "test -e /opt/logs/sidecar.log" && - # test $(kubectl exec sidecar-logtofile-pythonscript -- sh -c 'cat /opt/logs/sidecar.log | grep "Hello from python script!" | wc -l') = "16" + - name: Verify sidecar-req-once-per-batch logs after initial sync + run: | + test $(cat /tmp/logs/sidecar-req-once-per-batch.log | grep "Request once per batch will be enabled" | wc -l) = "1" && + test $(cat /tmp/logs/sidecar-req-once-per-batch.log | grep "Starting batch request" | wc -l) = "2" diff --git a/test/resources/sidecar.yaml b/test/resources/sidecar.yaml index fea448bd..5104d57a 100644 --- a/test/resources/sidecar.yaml +++ b/test/resources/sidecar.yaml @@ -394,7 +394,7 @@ stringData: apiVersion: v1 kind: Pod metadata: - name: sidecar-batch-reload + name: sidecar-req-once-per-batch namespace: default spec: serviceAccountName: sample-acc