Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package k8s-sidecar for openSUSE:Factory 
checked in at 2021-01-19 16:01:33
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/k8s-sidecar (Old)
 and      /work/SRC/openSUSE:Factory/.k8s-sidecar.new.28504 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "k8s-sidecar"

Tue Jan 19 16:01:33 2021 rev:2 rq:861700 version:0.1.144

Changes:
--------
--- /work/SRC/openSUSE:Factory/k8s-sidecar/k8s-sidecar.changes  2020-06-29 
21:16:24.965422116 +0200
+++ /work/SRC/openSUSE:Factory/.k8s-sidecar.new.28504/k8s-sidecar.changes       
2021-01-19 16:01:51.227349078 +0100
@@ -1,0 +2,13 @@
+Fri Jan 08 12:39:24 UTC 2021 - rbr...@suse.com
+
+- Update to version 0.1.144:
+  * Add support for configmaps with binary data (#96)
+  * #98: only perform request if files where changed (#100)
+  * update Kubernetes library to v12.0.0 (#97)
+  * Improve versioning and tagging releases (#95)
+  * add kubeconfig
+  * Update libs (#90)
+  * Fix custom SLEEP_TIME
+  * Add multi-arch builds with docker buildx (#86)
+
+-------------------------------------------------------------------

Old:
----
  k8s-sidecar-0.1.75.tar.gz

New:
----
  k8s-sidecar-0.1.144.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ k8s-sidecar.spec ++++++
--- /var/tmp/diff_new_pack.zhvuDC/_old  2021-01-19 16:01:51.943350161 +0100
+++ /var/tmp/diff_new_pack.zhvuDC/_new  2021-01-19 16:01:51.943350161 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package k8s-sidecar
 #
-# Copyright (c) 2020 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2021 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -20,14 +20,14 @@
 %define import_path github.com/kiwigrid/k8s-sidecar
 
 Name:               k8s-sidecar
-Version:            0.1.75
+Version:        0.1.144
 Release:            0
 Summary:            Collect kubernetes cluster configmaps and store it
 License:            MIT
 Group:              Development/Languages/Python
 URL:                https://github.com/kiwigrid/k8s-sidecar
 Source0:            %{name}-%{version}.tar.gz
-BuildArchitectures: noarch
+BuildArch:      noarch
 Requires:           python3
 Requires:           python3-kubernetes
 Requires:           python3-requests

++++++ _service ++++++
--- /var/tmp/diff_new_pack.zhvuDC/_old  2021-01-19 16:01:51.983350221 +0100
+++ /var/tmp/diff_new_pack.zhvuDC/_new  2021-01-19 16:01:51.983350221 +0100
@@ -3,8 +3,8 @@
     <param name="url">https://github.com/kiwigrid/k8s-sidecar.git</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="versionformat">0.1.75</param>
-    <param name="revision">0.1.75</param>
+    <param name="versionformat">0.1.144</param>
+    <param name="revision">0.1.144</param>
     <param name="changesgenerate">enable</param>
   </service>
   <service name="tar" mode="disabled"/>

++++++ k8s-sidecar-0.1.75.tar.gz -> k8s-sidecar-0.1.144.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k8s-sidecar-0.1.75/.circleci/config.yml 
new/k8s-sidecar-0.1.144/.circleci/config.yml
--- old/k8s-sidecar-0.1.75/.circleci/config.yml 2020-02-04 11:47:23.000000000 
+0100
+++ new/k8s-sidecar-0.1.144/.circleci/config.yml        2020-12-12 
10:59:23.000000000 +0100
@@ -3,7 +3,7 @@
 jobs:
   build:
     docker:
-      - image: circleci/python:3.7.4
+      - image: circleci/python:3.7.8
 
     working_directory: ~/repo
 
@@ -38,24 +38,35 @@
       #    destination: test-reports
 
       - setup_remote_docker:
-          docker_layer_caching: true
+          version: 19.03.12
 
       - run: |
-          TAG=0.1.$CIRCLE_BUILD_NUM
-          echo "$TAG" > /tmp/VERSION
-          docker build -t   kiwigrid/k8s-sidecar-build:$TAG . 
+          if [[ -z "${CIRCLE_TAG}" ]]; then
+            TAG=0.1.$CIRCLE_BUILD_NUM
+          else
+            TAG="${CIRCLE_TAG}"
+          fi
+          echo "$TAG" | tee /tmp/VERSION
+          mkdir -vp ~/.docker/cli-plugins/
+          curl --silent -L 
"https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.linux-amd64";
 > ~/.docker/cli-plugins/docker-buildx
+          chmod a+x ~/.docker/cli-plugins/docker-buildx
+          docker buildx version
+          docker run --rm --privileged multiarch/qemu-user-static --reset -p 
yes
+          docker context create buildcontext
+          docker buildx create buildcontext --use
           docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
-          docker push kiwigrid/k8s-sidecar-build:$TAG
+          docker buildx build --push -t 
${CIRCLE_PROJECT_USERNAME}/k8s-sidecar-build:$TAG --platform 
linux/amd64,linux/arm64,linux/arm/v7 .
+
       - persist_to_workspace:
           root: /tmp
           paths:
             - VERSION
 
-  test-k8s-1-13:
+  test-k8s-1-14:
     machine: true
     environment:
-      KIND_VERSION: v0.7.0
-      K8S_VERSION: v1.13.10
+      KIND_VERSION: v0.8.1
+      K8S_VERSION: v1.14.10
     steps:
       - attach_workspace:
           at: /tmp/
@@ -63,17 +74,18 @@
       - run: |
           VERSION=`cat /tmp/VERSION`
           sed -i 's/SIDECAR_VERSION/'"$VERSION"'/g' .circleci/test/sidecar.yaml
+          sed -i 's/CIRCLE_PROJECT_USERNAME/'"$CIRCLE_PROJECT_USERNAME"'/g' 
.circleci/test/sidecar.yaml
           cat .circleci/test/sidecar.yaml
       - run:
           name: test-in-cluster
           command: .circleci/test-in-cluster.sh
           no_output_timeout: 3600
 
-  test-k8s-1-14:
+  test-k8s-1-15:
     machine: true
     environment:
-      KIND_VERSION: v0.7.0
-      K8S_VERSION: v1.14.6
+      KIND_VERSION: v0.8.1
+      K8S_VERSION: v1.15.11
     steps:
       - attach_workspace:
           at: /tmp/
@@ -81,17 +93,18 @@
       - run: |
           VERSION=`cat /tmp/VERSION`
           sed -i 's/SIDECAR_VERSION/'"$VERSION"'/g' .circleci/test/sidecar.yaml
+          sed -i 's/CIRCLE_PROJECT_USERNAME/'"$CIRCLE_PROJECT_USERNAME"'/g' 
.circleci/test/sidecar.yaml
           cat .circleci/test/sidecar.yaml
       - run:
           name: test-in-cluster
           command: .circleci/test-in-cluster.sh
           no_output_timeout: 3600
 
-  test-k8s-1-15:
+  test-k8s-1-16:
     machine: true
     environment:
-      KIND_VERSION: v0.7.0
-      K8S_VERSION: v1.15.3
+      KIND_VERSION: v0.8.1
+      K8S_VERSION: v1.16.9
     steps:
       - attach_workspace:
           at: /tmp/
@@ -99,17 +112,18 @@
       - run: |
           VERSION=`cat /tmp/VERSION`
           sed -i 's/SIDECAR_VERSION/'"$VERSION"'/g' .circleci/test/sidecar.yaml
+          sed -i 's/CIRCLE_PROJECT_USERNAME/'"$CIRCLE_PROJECT_USERNAME"'/g' 
.circleci/test/sidecar.yaml
           cat .circleci/test/sidecar.yaml
       - run:
           name: test-in-cluster
           command: .circleci/test-in-cluster.sh
           no_output_timeout: 3600
 
-  test-k8s-1-16:
+  test-k8s-1-17:
     machine: true
     environment:
-      KIND_VERSION: v0.7.0
-      K8S_VERSION: v1.16.2
+      KIND_VERSION: v0.8.1
+      K8S_VERSION: v1.17.5
     steps:
       - attach_workspace:
           at: /tmp/
@@ -117,6 +131,45 @@
       - run: |
           VERSION=`cat /tmp/VERSION`
           sed -i 's/SIDECAR_VERSION/'"$VERSION"'/g' .circleci/test/sidecar.yaml
+          sed -i 's/CIRCLE_PROJECT_USERNAME/'"$CIRCLE_PROJECT_USERNAME"'/g' 
.circleci/test/sidecar.yaml
+          cat .circleci/test/sidecar.yaml
+      - run:
+          name: test-in-cluster
+          command: .circleci/test-in-cluster.sh
+          no_output_timeout: 3600
+
+  test-k8s-1-18:
+    machine: true
+    environment:
+      KIND_VERSION: v0.8.1
+      K8S_VERSION: v1.18.8
+    steps:
+      - attach_workspace:
+          at: /tmp/
+      - checkout
+      - run: |
+          VERSION=`cat /tmp/VERSION`
+          sed -i 's/SIDECAR_VERSION/'"$VERSION"'/g' .circleci/test/sidecar.yaml
+          sed -i 's/CIRCLE_PROJECT_USERNAME/'"$CIRCLE_PROJECT_USERNAME"'/g' 
.circleci/test/sidecar.yaml
+          cat .circleci/test/sidecar.yaml
+      - run:
+          name: test-in-cluster
+          command: .circleci/test-in-cluster.sh
+          no_output_timeout: 3600
+
+  test-k8s-1-19:
+    machine: true
+    environment:
+      KIND_VERSION: v0.8.1
+      K8S_VERSION: v1.19.0
+    steps:
+      - attach_workspace:
+          at: /tmp/
+      - checkout
+      - run: |
+          VERSION=`cat /tmp/VERSION`
+          sed -i 's/SIDECAR_VERSION/'"$VERSION"'/g' .circleci/test/sidecar.yaml
+          sed -i 's/CIRCLE_PROJECT_USERNAME/'"$CIRCLE_PROJECT_USERNAME"'/g' 
.circleci/test/sidecar.yaml
           cat .circleci/test/sidecar.yaml
       - run:
           name: test-in-cluster
@@ -125,46 +178,80 @@
 
   deploy:
     docker:
-      - image: circleci/python:3.7.4
+      - image: circleci/python:3.7.8
     steps:
       - attach_workspace:
           at: /tmp/
       - checkout
       - setup_remote_docker:
-          docker_layer_caching: true
+          version: 19.03.12
 
       - run: |
           TAG=`cat /tmp/VERSION`
+          mkdir -vp ~/.docker/cli-plugins/
+          curl --silent -L 
"https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.linux-amd64";
 > ~/.docker/cli-plugins/docker-buildx
+          chmod a+x ~/.docker/cli-plugins/docker-buildx
+          docker buildx version
+          docker run --rm --privileged multiarch/qemu-user-static --reset -p 
yes
+          docker context create buildcontext
+          docker buildx create buildcontext --use
           docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
-          docker pull kiwigrid/k8s-sidecar-build:$TAG
-          docker tag kiwigrid/k8s-sidecar-build:$TAG kiwigrid/k8s-sidecar:$TAG
-          docker tag kiwigrid/k8s-sidecar-build:$TAG 
kiwigrid/k8s-sidecar:latest
-          docker push kiwigrid/k8s-sidecar:$TAG
-          docker push kiwigrid/k8s-sidecar:latest
+          echo "FROM ${CIRCLE_PROJECT_USERNAME}/k8s-sidecar-build:$TAG" | 
docker buildx build --push -t ${CIRCLE_PROJECT_USERNAME}/k8s-sidecar:$TAG -t 
${CIRCLE_PROJECT_USERNAME}/k8s-sidecar:latest --platform 
linux/amd64,linux/arm64,linux/arm/v7 -
 
 workflows:
   version: 2
   test_deploy:
     jobs:
-      - build
-      - test-k8s-1-13:
-          requires:
-            - build
+      - build:
+          filters:  # required since `deploy` has tag filters AND requires 
`build`
+            tags:
+              only: /.*/
       - test-k8s-1-14:
+          filters:  # required since `deploy` has tag filters AND requires 
`build`
+            tags:
+              only: /.*/
           requires:
             - build
       - test-k8s-1-15:
+          filters:  # required since `deploy` has tag filters AND requires 
`build`
+            tags:
+              only: /.*/
           requires:
             - build
       - test-k8s-1-16:
+          filters:  # required since `deploy` has tag filters AND requires 
`build`
+            tags:
+              only: /.*/
+          requires:
+            - build
+      - test-k8s-1-17:
+          filters:  # required since `deploy` has tag filters AND requires 
`build`
+            tags:
+              only: /.*/
+          requires:
+            - build
+      - test-k8s-1-18:
+          filters:  # required since `deploy` has tag filters AND requires 
`build`
+            tags:
+              only: /.*/
+          requires:
+            - build
+      - test-k8s-1-19:
+          filters:  # required since `deploy` has tag filters AND requires 
`build`
+            tags:
+              only: /.*/
           requires:
             - build
       - deploy:
           requires:
-            - test-k8s-1-13
             - test-k8s-1-14
             - test-k8s-1-15
             - test-k8s-1-16
+            - test-k8s-1-17
+            - test-k8s-1-18
+            - test-k8s-1-19
           filters:
             branches:
               only: master
+            tags:
+              only: /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k8s-sidecar-0.1.75/.circleci/test/configmap.yaml 
new/k8s-sidecar-0.1.144/.circleci/test/configmap.yaml
--- old/k8s-sidecar-0.1.75/.circleci/test/configmap.yaml        2020-02-04 
11:47:23.000000000 +0100
+++ new/k8s-sidecar-0.1.144/.circleci/test/configmap.yaml       2020-12-12 
10:59:23.000000000 +0100
@@ -6,4 +6,13 @@
     findme: "yea"
 data:
   hello.world: |-
-     Hello World!
\ No newline at end of file
+     Hello World!
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: sample-configmap-binary
+  labels:
+    findme: "yea"
+binaryData:
+  hello.binary: 
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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k8s-sidecar-0.1.75/.circleci/test/sidecar.yaml 
new/k8s-sidecar-0.1.144/.circleci/test/sidecar.yaml
--- old/k8s-sidecar-0.1.75/.circleci/test/sidecar.yaml  2020-02-04 
11:47:23.000000000 +0100
+++ new/k8s-sidecar-0.1.144/.circleci/test/sidecar.yaml 2020-12-12 
10:59:23.000000000 +0100
@@ -34,7 +34,7 @@
   serviceAccountName: sample-acc
   containers:
   - name: sidecar
-    image: kiwigrid/k8s-sidecar-build:SIDECAR_VERSION
+    image: CIRCLE_PROJECT_USERNAME/k8s-sidecar-build:SIDECAR_VERSION
     volumeMounts:
     - name: shared-volume
       mountPath: /tmp/
@@ -47,4 +47,4 @@
       value: both
   volumes:
   - name: shared-volume
-    emptyDir: {}
\ No newline at end of file
+    emptyDir: {}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k8s-sidecar-0.1.75/.circleci/test-in-cluster.sh 
new/k8s-sidecar-0.1.144/.circleci/test-in-cluster.sh
--- old/k8s-sidecar-0.1.75/.circleci/test-in-cluster.sh 2020-02-04 
11:47:23.000000000 +0100
+++ new/k8s-sidecar-0.1.144/.circleci/test-in-cluster.sh        2020-12-12 
10:59:23.000000000 +0100
@@ -68,7 +68,8 @@
   }
 
   verify_configmap_read(){
-    kubectl exec sidecar ls /tmp/hello.world
+    kubectl exec sidecar -- ls /tmp/hello.world
+    kubectl exec sidecar -- ls /tmp/hello.binary
   }
 
   cleanup_cluster() {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k8s-sidecar-0.1.75/.github/workflows/main.yaml 
new/k8s-sidecar-0.1.144/.github/workflows/main.yaml
--- old/k8s-sidecar-0.1.75/.github/workflows/main.yaml  1970-01-01 
01:00:00.000000000 +0100
+++ new/k8s-sidecar-0.1.144/.github/workflows/main.yaml 2020-12-12 
10:59:23.000000000 +0100
@@ -0,0 +1,18 @@
+name: Bump version
+on:
+  push:
+    branches:
+      - master
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        fetch-depth: '0'
+    - name: Bump version and push tag
+      uses: anothrNick/github-tag-action@1.26.0
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        REPO_OWNER: pulledtim
+        INITIAL_VERSION: 1.0.0
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k8s-sidecar-0.1.75/.gitignore 
new/k8s-sidecar-0.1.144/.gitignore
--- old/k8s-sidecar-0.1.75/.gitignore   2020-02-04 11:47:23.000000000 +0100
+++ new/k8s-sidecar-0.1.144/.gitignore  1970-01-01 01:00:00.000000000 +0100
@@ -1,2 +0,0 @@
-*.iml
-.idea/
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k8s-sidecar-0.1.75/Dockerfile 
new/k8s-sidecar-0.1.144/Dockerfile
--- old/k8s-sidecar-0.1.75/Dockerfile   2020-02-04 11:47:23.000000000 +0100
+++ new/k8s-sidecar-0.1.144/Dockerfile  2020-12-12 10:59:23.000000000 +0100
@@ -4,11 +4,14 @@
 
 COPY        requirements.txt .
 RUN         apk add --no-cache gcc && \
-           pip install -r requirements.txt && \
-           apk del gcc
+           pip3 install -r requirements.txt && \
+           apk del -r gcc && \
+            rm -rf /var/cache/apk/* requirements.txt
 
 COPY sidecar/* ./
 
-#run as non-privileged user 
-USER nobody
+# Use the nobody user's numeric UID/GID to satisfy MustRunAsNonRoot 
PodSecurityPolicies
+# 
https://kubernetes.io/docs/concepts/policy/pod-security-policy/#users-and-groups
+USER 65534:65534
+
 CMD         [ "python", "-u", "/app/sidecar.py" ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k8s-sidecar-0.1.75/README.md 
new/k8s-sidecar-0.1.144/README.md
--- old/k8s-sidecar-0.1.75/README.md    2020-02-04 11:47:23.000000000 +0100
+++ new/k8s-sidecar-0.1.144/README.md   2020-12-12 10:59:23.000000000 +0100
@@ -5,7 +5,7 @@
 
 # What?
 
-This is a docker container intended to run inside a kubernetes cluster to 
collect config maps with a specified label and store the included files in an 
local folder. It can also send an HTTP request to a specified URL after a 
configmap change. The main target is to be run as a sidecar container to supply 
an application with information from the cluster. The contained python script 
is working with the Kubernetes API 1.10
+This is a docker container intended to run inside a kubernetes cluster to 
collect config maps with a specified label and store the included files in an 
local folder. It can also send an HTTP request to a specified URL after a 
configmap change. The main target is to be run as a sidecar container to supply 
an application with information from the cluster. The contained Python script 
is working from Kubernetes API 1.10.
 
 # Why?
 
@@ -13,7 +13,7 @@
 
 # How?
 
-Run the container created by this repo together you application in an single 
pod with a shared volume. Specify which label should be monitored and where the 
files should be stored.
+Run the container created by this repo together with your application in an 
single pod with a shared volume. Specify which label should be monitored and 
where the files should be stored.
 By adding additional env variables the container can send an HTTP request to 
specified URL.
 
 # Features
@@ -21,13 +21,14 @@
 - Extract files from config maps
 - Filter based on label
 - Update/Delete on change of configmap
+- Enforce unique filenames
 
 # Usage
 
-Example for a simple deployment can be found in `example.yaml`. Depending on 
the cluster setup you have to grant yourself admin rights first: `kubectl 
create clusterrolebinding cluster-admin-binding   --clusterrole cluster-admin   
--user $(gcloud config get-value account)`
+Example for a simple deployment can be found in 
[`example.yaml`](./example.yaml). Depending on the cluster setup you have to 
grant yourself admin rights first: `kubectl create clusterrolebinding 
cluster-admin-binding --clusterrole cluster-admin   --user $(gcloud config 
get-value account)`
 
 One can override the default directory that files are copied into using a 
configmap annotation defined by the environment variable "FOLDER_ANNOTATION" 
(if not present it will default to "k8s-sidecar-target-directory"). The sidecar 
will attempt to create directories defined by configmaps if they are not 
present. Example configmap annotation:
-  k8s-sidecar-target-directory: "/path/to/target/directory"
+  `k8s-sidecar-target-directory: "/path/to/target/directory"`
 
 If the filename ends with `.url` suffix, the content will be processed as an 
URL the target file will be downloaded and used as the content file.
 
@@ -38,6 +39,11 @@
   - required: true
   - type: string
 
+- `LABEL_VALUE`
+  - description: The value for the label you want to filter your resources on. 
Don't set a value to filter by any value
+  - required: false
+  - type: string
+
 - `FOLDER`
   - description: Folder where the files should be placed
   - required: true
@@ -60,15 +66,26 @@
   - type: string
 
 - `METHOD`
-  - description: If `METHOD` is set with `LIST`, the sidecar will just list 
config-maps and exit. With `SLEEP` it will list all config-maps, then sleep for 
60 seconds. Default is watch.
+  - description: If `METHOD` is set with `LIST`, the sidecar will just list 
config-maps and exit. With `SLEEP` it will list all config-maps, then sleep for 
`SLEEP_TIME` seconds. Default is watch.
   - required: false
   - type: string
 
+- `SLEEP_TIME`
+  - description: How many seconds to wait before updating config-maps when 
using `SLEEP` method.
+  - required: false
+  - default: 60
+  - type: integer
+
 - `REQ_URL`
   - description: URL to which send a request after a configmap got reloaded
   - required: false
   - type: URI
 
+- `REQ_USERNAME`
+  - description: Username to use for basic authentication
+  - required: false
+  - type: string
+
 - `REQ_METHOD`
   - description: Request method GET(default) or POST
   - required: false
@@ -79,6 +96,11 @@
   - required: false
   - type: json
 
+- `REQ_PASSWORD`
+  - description: Password to use for basic authentication
+  - required: false
+  - type: string
+
 - `REQ_RETRY_TOTAL`
   - description: Total number of retries to allow
   - required: false
@@ -104,12 +126,29 @@
   - type: float
 
 - `REQ_TIMEOUT`
-  - description: many seconds to wait for the server to send data before 
giving up
+  - description: How many seconds to wait for the server to send data before 
giving up
   - required: false
   - default: 10
   - type: float
 
+- `ERROR_THROTTLE_SLEEP`
+  - description: How many seconds to wait before watching resources again when 
an error occurs
+  - required: false
+  - default: 5
+  - type: integer
+
 - `SKIP_TLS_VERIFY`
   - description: Set to true to skip tls verification for kube api calls
   - required: false
   - type: boolean
+
+- `UNIQUE_FILENAMES`
+  - description: Set to true to produce unique filenames where duplicate data 
keys exist between ConfigMaps and/or Secrets within the same or multiple 
Namespaces.
+  - required: false
+  - default: false
+  - type: boolean
+
+- `DEFAULT_FILE_MODE`
+  - description: The default file system permission for every file. Use three 
digits (e.g. '500', '440', ...)
+  - required: false
+  - type: string
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k8s-sidecar-0.1.75/requirements.txt 
new/k8s-sidecar-0.1.144/requirements.txt
--- old/k8s-sidecar-0.1.75/requirements.txt     2020-02-04 11:47:23.000000000 
+0100
+++ new/k8s-sidecar-0.1.144/requirements.txt    2020-12-12 10:59:23.000000000 
+0100
@@ -1,2 +1,2 @@
-kubernetes==10.0.0
-requests==2.22.0
+kubernetes==12.0.0
+requests==2.24.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k8s-sidecar-0.1.75/sidecar/helpers.py 
new/k8s-sidecar-0.1.144/sidecar/helpers.py
--- old/k8s-sidecar-0.1.75/sidecar/helpers.py   2020-02-04 11:47:23.000000000 
+0100
+++ new/k8s-sidecar-0.1.144/sidecar/helpers.py  2020-12-12 10:59:23.000000000 
+0100
@@ -1,40 +1,87 @@
+#!/usr/bin/env python
+
+import hashlib
 import os
 import errno
 
 import requests
 from requests.packages.urllib3.util.retry import Retry
 from requests.adapters import HTTPAdapter
+from datetime import datetime
 
 
-def writeTextToFile(folder, filename, data):
+def writeTextToFile(folder, filename, data, data_type="ascii"):
+    """
+    Write text to a file. If the parent folder doesn't exist, create it. If 
there are insufficient
+    permissions to create the directory, log an error and return.
+    """
     if not os.path.exists(folder):
         try:
             os.makedirs(folder)
         except OSError as e:
-            if e.errno != errno.EEXIST:
+            if e.errno not in (errno.EACCES, errno.EEXIST):
                 raise
+            if e.errno == errno.EACCES:
+                print(f"{timestamp()} Error: insufficient privileges to create 
{folder}. "
+                      f"Skipping {filename}.")
+                return
+
+    absolutepath = os.path.join(folder, filename)
+    if os.path.exists(absolutepath):
+        # Compare file contents with new ones so we don't update the file if 
nothing changed
+        if data_type == "binary":
+            sha256_hash_new = hashlib.sha256(data)
+        else:
+            sha256_hash_new = hashlib.sha256(data.encode('utf-8'))
+
+        with open(absolutepath, 'rb') as f:
+            sha256_hash_cur = hashlib.sha256()
+            for byte_block in iter(lambda: f.read(4096),b""):
+                sha256_hash_cur.update(byte_block)
+
+        if sha256_hash_new.hexdigest() == sha256_hash_cur.hexdigest():
+            print(f"{timestamp()} Contents of {filename} haven't changed. Not 
overwriting existing file")
+            return False
+
+    if data_type == "ascii":
+        write_type = "w"
+    elif data_type == "binary":
+        write_type = "wb"
 
-    with open(folder + "/" + filename, 'w') as f:
+    with open(absolutepath, write_type) as f:
         f.write(data)
         f.close()
+    if os.getenv('DEFAULT_FILE_MODE'):
+        mode = int(os.getenv('DEFAULT_FILE_MODE'), base=8)
+        os.chmod(absolutepath, mode)
+    return True
 
 
 def removeFile(folder, filename):
-    completeFile = folder + "/" + filename
+    completeFile = os.path.join(folder, filename)
     if os.path.isfile(completeFile):
         os.remove(completeFile)
+        return True
     else:
-        print(f"Error: {completeFile} file not found")
+        print(f"{timestamp()} Error: {completeFile} file not found")
+        return False
 
 
 def request(url, method, payload=None):
-    retryTotal = 5 if os.getenv('REQ_RETRY_TOTAL') is None else 
int(os.getenv('REQ_RETRY_TOTAL'))
-    retryConnect = 5 if os.getenv('REQ_RETRY_CONNECT') is None else int(
-        os.getenv('REQ_RETRY_CONNECT'))
-    retryRead = 5 if os.getenv('REQ_RETRY_READ') is None else 
int(os.getenv('REQ_RETRY_READ'))
-    retryBackoffFactor = 0.2 if os.getenv('REQ_RETRY_BACKOFF_FACTOR') is None 
else float(
-        os.getenv('REQ_RETRY_BACKOFF_FACTOR'))
-    timeout = 10 if os.getenv('REQ_TIMEOUT') is None else 
float(os.getenv('REQ_TIMEOUT'))
+    retryTotal = 5 if os.getenv("REQ_RETRY_TOTAL") is None else 
int(os.getenv("REQ_RETRY_TOTAL"))
+    retryConnect = 5 if os.getenv("REQ_RETRY_CONNECT") is None else int(
+        os.getenv("REQ_RETRY_CONNECT"))
+    retryRead = 5 if os.getenv("REQ_RETRY_READ") is None else 
int(os.getenv("REQ_RETRY_READ"))
+    retryBackoffFactor = 0.2 if os.getenv("REQ_RETRY_BACKOFF_FACTOR") is None 
else float(
+        os.getenv("REQ_RETRY_BACKOFF_FACTOR"))
+    timeout = 10 if os.getenv("REQ_TIMEOUT") is None else 
float(os.getenv("REQ_TIMEOUT"))
+
+    username = os.getenv("REQ_USERNAME")
+    password = os.getenv("REQ_PASSWORD")
+    if username and password:
+        auth = (username, password)
+    else:
+        auth = None
 
     r = requests.Session()
     retries = Retry(total=retryTotal,
@@ -42,16 +89,38 @@
                     read=retryRead,
                     backoff_factor=retryBackoffFactor,
                     status_forcelist=[500, 502, 503, 504])
-    r.mount('http://', HTTPAdapter(max_retries=retries))
-    r.mount('https://', HTTPAdapter(max_retries=retries))
+    r.mount("http://";, HTTPAdapter(max_retries=retries))
+    r.mount("https://";, HTTPAdapter(max_retries=retries))
     if url is None:
-        print("No url provided. Doing nothing.")
+        print(f"{timestamp()} No url provided. Doing nothing.")
         return
 
     # If method is not provided use GET as default
     if method == "GET" or not method:
-        res = r.get("%s" % url, timeout=timeout)
+        res = r.get("%s" % url, auth=auth, timeout=timeout)
     elif method == "POST":
-        res = r.post("%s" % url, json=payload, timeout=timeout)
-        print(f"{method} request sent to {url}. Response: {res.status_code} 
{res.reason}")
+        res = r.post("%s" % url, auth=auth, json=payload, timeout=timeout)
+        print(f"{timestamp()} {method} request sent to {url}. "
+              f"Response: {res.status_code} {res.reason} {res.text}")
     return res
+
+
+def timestamp():
+    """Get a timestamp of the current time for logging."""
+    return datetime.now().strftime("[%Y-%m-%d %X]")
+
+
+def uniqueFilename(filename, namespace, resource, resource_name):
+    """Return a unique filename derived from the arguments provided, e.g.
+    "namespace_{namespace}.{configmap|secret}_{resource_name}.{filename}".
+
+    This is used where duplicate data keys may exist between ConfigMaps
+    and/or Secrets within the same or multiple Namespaces.
+
+    Keyword arguments:
+    filename -- the filename derived from a data key present in a ConfigMap or 
Secret.
+    namespace -- the Namespace from which data is sourced.
+    resource -- the resource type, e.g. "configmap" or "secret".
+    resource_name -- the name of the "configmap" or "secret" resource instance.
+    """
+    return "namespace_" + namespace + "." + resource + "_" + resource_name + 
"." + filename
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k8s-sidecar-0.1.75/sidecar/resources.py 
new/k8s-sidecar-0.1.144/sidecar/resources.py
--- old/k8s-sidecar-0.1.75/sidecar/resources.py 2020-02-04 11:47:23.000000000 
+0100
+++ new/k8s-sidecar-0.1.144/sidecar/resources.py        2020-12-12 
10:59:23.000000000 +0100
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
 import base64
 import os
 import sys
@@ -9,8 +11,9 @@
 from kubernetes import client, watch
 from kubernetes.client.rest import ApiException
 from urllib3.exceptions import ProtocolError
+from urllib3.exceptions import MaxRetryError
 
-from helpers import request, writeTextToFile, removeFile
+from helpers import request, writeTextToFile, removeFile, timestamp, 
uniqueFilename
 
 _list_namespaced = {
     "secret": "list_namespaced_secret",
@@ -18,7 +21,7 @@
 }
 
 def signal_handler(signum, frame):
-    print("Subprocess exiting gracefully")
+    print(f"{timestamp()} Subprocess exiting gracefully")
     sys.exit(0)
 
 signal.signal(signal.SIGTERM, signal_handler)
@@ -29,9 +32,11 @@
 }
 
 
-def _get_file_data_and_name(full_filename, content, resource):
+def _get_file_data_and_name(full_filename, content, resource, 
content_type="ascii"):
     if resource == "secret":
         file_data = base64.b64decode(content).decode()
+    elif content_type == "binary":
+        file_data = base64.decodebytes(content.encode('ascii'))
     else:
         file_data = content
 
@@ -44,137 +49,197 @@
     return filename, file_data
 
 
-def listResources(label, targetFolder, url, method, payload, current, 
folderAnnotation, resource):
+def _get_destination_folder(metadata, defaultFolder, folderAnnotation):
+    if metadata.annotations:
+        if folderAnnotation in metadata.annotations.keys():
+            destFolder = metadata.annotations[folderAnnotation]
+            print(f"{timestamp()} Found a folder override annotation, "
+                  f"placing the {metadata.name} in: {destFolder}")
+            return destFolder
+    return defaultFolder
+
+
+def listResources(label, labelValue, targetFolder, url, method, payload,
+                  currentNamespace, folderAnnotation, resource, 
uniqueFilenames):
     v1 = client.CoreV1Api()
-    namespace = os.getenv("NAMESPACE", current)
+    namespace = os.getenv("NAMESPACE", currentNamespace)
+    # Filter resources based on label and value or just label
+    labelSelector=f"{label}={labelValue}" if labelValue else label
+
     if namespace == "ALL":
-        ret = getattr(v1, _list_for_all_namespaces[resource])()
+        ret = getattr(v1, 
_list_for_all_namespaces[resource])(label_selector=labelSelector)
     else:
-        ret = getattr(v1, _list_namespaced[resource])(namespace=namespace)
+        ret = getattr(v1, _list_namespaced[resource])(namespace=namespace, 
label_selector=labelSelector)
+
+    files_changed = False
 
+    # For all the found resources
     for sec in ret.items:
-        destFolder = targetFolder
         metadata = sec.metadata
-        if metadata.labels is None:
-            continue
-        print(f'Working on {resource}: {metadata.namespace}/{metadata.name}')
-        if label in sec.metadata.labels.keys():
-            print(f"Found {resource} with label")
-            if sec.metadata.annotations is not None:
-                if folderAnnotation in sec.metadata.annotations.keys():
-                    destFolder = sec.metadata.annotations[folderAnnotation]
-
-            dataMap = sec.data
-            if dataMap is None:
-                print(f"No data field in {resource}")
+
+        print(f"{timestamp()} Working on {resource}: 
{metadata.namespace}/{metadata.name}")
+
+        # Get the destination folder
+        destFolder = _get_destination_folder(metadata, targetFolder, 
folderAnnotation)
+
+        # Check if it's an empty ConfigMap or Secret
+        if resource == "configmap":
+            if sec.data is None and sec.binary_data is None:
+                print(f"{timestamp()} No data/binaryData field in {resource}")
                 continue
+        else:
+            if sec.data is None:
+                print(f"{timestamp()} No data field in {resource}")
+                continue
+
+        # Each key on the data is a file
+        if sec.data is not None:
+            for data_key in sec.data.keys():
+                filename, filedata = _get_file_data_and_name(data_key,
+                                                             
sec.data[data_key],
+                                                             resource)
+                if uniqueFilenames:
+                    filename = uniqueFilename(filename      = filename,
+                                              namespace     = 
metadata.namespace,
+                                              resource      = resource,
+                                              resource_name = metadata.name)
+
+                files_changed |= writeTextToFile(destFolder, filename, 
filedata)
+
+        # Each key on the binaryData is a file
+        if sec.binary_data is not None:
+            for data_key in sec.binary_data.keys():
+                filename, filedata = _get_file_data_and_name(data_key,
+                                                             
sec.binary_data[data_key],
+                                                             resource,
+                                                             
content_type="binary")
+                if uniqueFilenames:
+                    filename = uniqueFilename(filename      = filename,
+                                              namespace     = 
metadata.namespace,
+                                              resource      = resource,
+                                              resource_name = metadata.name)
 
-            if label in sec.metadata.labels.keys():
-                for data_key in dataMap.keys():
-                    filename, filedata = _get_file_data_and_name(data_key, 
dataMap[data_key],
-                                                                 resource)
-                    writeTextToFile(destFolder, filename, filedata)
+                files_changed |= writeTextToFile(destFolder, filename, 
filedata, data_type="binary")
 
-                    if url is not None:
-                        request(url, method, payload)
+    if url and files_changed:
+        request(url, method, payload)
 
 
-def _watch_resource_iterator(label, targetFolder, url, method, payload,
-                             current, folderAnnotation, resource):
+def _watch_resource_iterator(label, labelValue, targetFolder, url, method, 
payload,
+                             currentNamespace, folderAnnotation, resource, 
uniqueFilenames):
     v1 = client.CoreV1Api()
-    namespace = os.getenv("NAMESPACE", current)
+    namespace = os.getenv("NAMESPACE", currentNamespace)
+    # Filter resources based on label and value or just label
+    labelSelector=f"{label}={labelValue}" if labelValue else label
+
     if namespace == "ALL":
-        stream = watch.Watch().stream(getattr(v1, 
_list_for_all_namespaces[resource]))
+        stream = watch.Watch().stream(getattr(v1, 
_list_for_all_namespaces[resource]), label_selector=labelSelector)
     else:
-        stream = watch.Watch().stream(getattr(v1, _list_namespaced[resource]), 
namespace=namespace)
+        stream = watch.Watch().stream(getattr(v1, _list_namespaced[resource]), 
namespace=namespace, label_selector=labelSelector)
 
+    # Process events
     for event in stream:
-        destFolder = targetFolder
-        metadata = event['object'].metadata
-        if metadata.labels is None:
+        metadata = event["object"].metadata
+
+        print(f"{timestamp()} Working on {resource} 
{metadata.namespace}/{metadata.name}")
+
+        files_changed = False
+
+        # Get the destination folder
+        destFolder = _get_destination_folder(metadata, targetFolder, 
folderAnnotation)
+
+        # Check if it's an empty ConfigMap or Secret
+        dataMap = event["object"].data
+        if dataMap is None:
+            print(f"{timestamp()} {resource} does not have data.")
             continue
-        print(f'Working on {resource} {metadata.namespace}/{metadata.name}')
-        if label in event['object'].metadata.labels.keys():
-            print(f"{resource} with label found")
-            if event['object'].metadata.annotations is not None:
-                if folderAnnotation in 
event['object'].metadata.annotations.keys():
-                    destFolder = 
event['object'].metadata.annotations[folderAnnotation]
-                    print('Found a folder override annotation, '
-                          f'placing the {resource} in: {destFolder}')
-            dataMap = event['object'].data
-            if dataMap is None:
-                print(f"{resource} does not have data.")
-                continue
-            eventType = event['type']
-            for data_key in dataMap.keys():
-                print(f"File in {resource} {data_key} {eventType}")
-
-                if (eventType == "ADDED") or (eventType == "MODIFIED"):
-                    filename, filedata = _get_file_data_and_name(data_key, 
dataMap[data_key],
-                                                                 resource)
-                    writeTextToFile(destFolder, filename, filedata)
-
-                    if url is not None:
-                        request(url, method, payload)
-                else:
-                    filename = data_key[:-4] if data_key.endswith(".url") else 
data_key
-                    removeFile(destFolder, filename)
-                    if url is not None:
-                        request(url, method, payload)
+
+        eventType = event["type"]
+        # Each key on the data is a file
+        for data_key in dataMap.keys():
+            print(f"{timestamp()} File in {resource} {data_key} {eventType}")
+
+            if (eventType == "ADDED") or (eventType == "MODIFIED"):
+                filename, filedata = _get_file_data_and_name(data_key, 
dataMap[data_key],
+                                                                resource)
+                if uniqueFilenames:
+                    filename = uniqueFilename(filename      = filename,
+                                              namespace     = 
metadata.namespace,
+                                              resource      = resource,
+                                              resource_name = metadata.name)
+
+                files_changed |= writeTextToFile(destFolder, filename, 
filedata)
+            else:
+                # Get filename from event
+                filename = data_key[:-4] if data_key.endswith(".url") else 
data_key
+
+                if uniqueFilenames:
+                    filename = uniqueFilename(filename      = filename,
+                                              namespace     = 
metadata.namespace,
+                                              resource      = resource,
+                                              resource_name = metadata.name)
+
+                files_changed |= removeFile(destFolder, filename)
+        if url and files_changed:
+            request(url, method, payload)
 
 
 def _watch_resource_loop(mode, *args):
     while True:
         try:
+            # Always wait to slow down the loop in case of exceptions
+            sleep(int(os.getenv("ERROR_THROTTLE_SLEEP", 5)))
             if mode == "SLEEP":
                 listResources(*args)
-                sleep(60)
+                sleep(int(os.getenv("SLEEP_TIME", 60)))
             else:
                 _watch_resource_iterator(*args)
         except ApiException as e:
             if e.status != 500:
-                print(f"ApiException when calling kubernetes: {e}\n")
+                print(f"{timestamp()} ApiException when calling kubernetes: 
{e}\n")
             else:
                 raise
         except ProtocolError as e:
-            print(f"ProtocolError when calling kubernetes: {e}\n")
+            print(f"{timestamp()} ProtocolError when calling kubernetes: 
{e}\n")
+        except MaxRetryError as e:
+            print(f"{timestamp()} MaxRetryError when calling kubernetes: 
{e}\n")
         except Exception as e:
-            print(f"Received unknown exception: {e}\n")
+            print(f"{timestamp()} Received unknown exception: {e}\n")
 
 
-def watchForChanges(mode, label, targetFolder, url, method, payload,
-                    current, folderAnnotation, resources):
+def watchForChanges(mode, label, labelValue, targetFolder, url, method, 
payload,
+                    currentNamespace, folderAnnotation, resources, 
uniqueFilenames):
 
     firstProc = Process(target=_watch_resource_loop,
-                        args=(mode, label, targetFolder, url, method, payload,
-                              current, folderAnnotation, resources[0])
+                        args=(mode, label, labelValue, targetFolder, url, 
method, payload,
+                              currentNamespace, folderAnnotation, 
resources[0], uniqueFilenames)
                         )
     firstProc.daemon=True
     firstProc.start()
 
     if len(resources) == 2:
         secProc = Process(target=_watch_resource_loop,
-                          args=(mode, label, targetFolder, url, method, 
payload,
-                                current, folderAnnotation, resources[1])
+                          args=(mode, label, labelValue, targetFolder, url, 
method, payload,
+                                currentNamespace, folderAnnotation, 
resources[1], uniqueFilenames)
                           )
         secProc.daemon=True
         secProc.start()
 
     while True:
         if not firstProc.is_alive():
-            print(f"Process for {resources[0]} died. Stopping and exiting")
+            print(f"{timestamp()} Process for {resources[0]} died. Stopping 
and exiting")
             if len(resources) == 2 and secProc.is_alive():
                 secProc.terminate()
             elif len(resources) == 2:
-                print(f"Process for {resources[1]}  also died...")
+                print(f"{timestamp()} Process for {resources[1]}  also 
died...")
             raise Exception("Loop died")
 
         if len(resources) == 2 and not secProc.is_alive():
-            print(f"Process for {resources[1]} died. Stopping and exiting")
+            print(f"{timestamp()} Process for {resources[1]} died. Stopping 
and exiting")
             if firstProc.is_alive():
                 firstProc.terminate()
             else:
-                print(f"Process for {resources[0]}  also died...")
+                print(f"{timestamp()} Process for {resources[0]}  also 
died...")
             raise Exception("Loop died")
 
         sleep(5)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/k8s-sidecar-0.1.75/sidecar/sidecar.py 
new/k8s-sidecar-0.1.144/sidecar/sidecar.py
--- old/k8s-sidecar-0.1.75/sidecar/sidecar.py   2020-02-04 11:47:23.000000000 
+0100
+++ new/k8s-sidecar-0.1.144/sidecar/sidecar.py  2020-12-12 10:59:23.000000000 
+0100
@@ -1,54 +1,73 @@
+#!/usr/bin/env python
+
 import os
 
 from kubernetes import client, config
 
 from resources import listResources, watchForChanges
 
+from helpers import timestamp
 
 def main():
-    print("Starting collector")
+    print(f"{timestamp()} Starting collector")
 
-    folderAnnotation = os.getenv('FOLDER_ANNOTATIONS')
+    folderAnnotation = os.getenv("FOLDER_ANNOTATION")
     if folderAnnotation is None:
-        print("No folder annotation was provided, defaulting to 
k8s-sidecar-target-directory")
+        print(f"{timestamp()} No folder annotation was provided, "
+              "defaulting to k8s-sidecar-target-directory")
         folderAnnotation = "k8s-sidecar-target-directory"
 
-    label = os.getenv('LABEL')
+    label = os.getenv("LABEL")
     if label is None:
-        print("Should have added LABEL as environment variable! Exit")
+        print(f"{timestamp()} Should have added LABEL as environment variable! 
Exit")
         return -1
 
-    targetFolder = os.getenv('FOLDER')
+    labelValue = os.getenv("LABEL_VALUE")
+    if labelValue:
+        print(f"{timestamp()} Filter labels with value: {labelValue}")
+
+    targetFolder = os.getenv("FOLDER")
     if targetFolder is None:
-        print("Should have added FOLDER as environment variable! Exit")
+        print(f"{timestamp()} Should have added FOLDER as environment 
variable! Exit")
         return -1
 
-    resources = os.getenv('RESOURCE', 'configmap')
+    resources = os.getenv("RESOURCE", "configmap")
     resources = ("secret", "configmap") if resources == "both" else 
(resources, )
-    print(f"Selected resource type: {resources}")
-
-    method = os.getenv('REQ_METHOD')
-    url = os.getenv('REQ_URL')
-    payload = os.getenv('REQ_PAYLOAD')
+    print(f"{timestamp()} Selected resource type: {resources}")
 
-    config.load_incluster_config()
-    print("Config for cluster api loaded...")
-    namespace = 
open("/var/run/secrets/kubernetes.io/serviceaccount/namespace").read()
+    method = os.getenv("REQ_METHOD")
+    url = os.getenv("REQ_URL")
+    payload = os.getenv("REQ_PAYLOAD")
+
+    try:
+      config.load_kube_config()
+    except:
+      config.load_incluster_config()
+    print(f"{timestamp()} Config for cluster api loaded...")
+    currentNamespace = 
open("/var/run/secrets/kubernetes.io/serviceaccount/namespace").read()
 
-    if os.getenv('SKIP_TLS_VERIFY') == 'true':
+    if os.getenv("SKIP_TLS_VERIFY") == "true":
         configuration = client.Configuration()
         configuration.verify_ssl = False
         configuration.debug = False
         client.Configuration.set_default(configuration)
 
+    uniqueFilenames = os.getenv("UNIQUE_FILENAMES") 
+    if uniqueFilenames is not None and uniqueFilenames.lower() == "true":
+        print(f"{timestamp()} Unique filenames will be enforced.")
+        uniqueFilenames = True
+    else:
+        print(f"{timestamp()} Unique filenames will not be enforced.")
+        uniqueFilenames = False
+
     if os.getenv("METHOD") == "LIST":
         for res in resources:
-            listResources(label, targetFolder, url, method, payload,
-                          namespace, folderAnnotation, res)
+            listResources(label, labelValue, targetFolder, url, method, 
payload,
+                          currentNamespace, folderAnnotation, res, 
uniqueFilenames)
     else:
-        watchForChanges(os.getenv("METHOD"), label, targetFolder, url, method,
-                        payload, namespace, folderAnnotation, resources)
+        watchForChanges(os.getenv("METHOD"), label, labelValue, targetFolder, 
url, method,
+                        payload, currentNamespace, folderAnnotation, 
resources, uniqueFilenames)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()

Reply via email to