0.9.1 (#113)
* fix: fix absolute path in run.sh
starting the operator locally gave error "No such file or directory task: Failed to run task "run": task: Failed to run task "d:run": exit status 127", because of an absolute path inside run.sh
* fix: path to check for db ready
changed the path for wait_db_ready. The path without the final / led to a 301 and not a 200
* fix: invoker and operator image from env
removed hardcoded values for cfg.get operator.image / operator.tag and invoker.image and invoker.tag. Config now check env vars and defaults to old images
* fix(test): milvus tests
fixed tests for minio / seaweedfs .
* chore(docs): taskfile
added desc to taskfile tasks
* fix: namespace validation
added namespace validation in user creation
* chore(deps): bump urllib3 from 2.3.0 to 2.7.0
---------
Co-authored-by: Michele Sciabarra <30654959+sciabarracom@users.noreply.github.com>
diff --git a/Taskfile.yml b/Taskfile.yml
index 16d412e..51341e5 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -74,10 +74,13 @@
tasks:
- default:
+ default:
+ desc: alias for "use" (show/select the current kubernetes cluster)
+ cmds:
- task: use
setup:
+ desc: setup the local development environment (deps, ssh key, env config, poetry)
deps:
- update-files-from-openwhisk
- ssh-key
@@ -88,55 +91,100 @@
status:
- test -e $GOBIN/kopf
- env: env
-
- watch: watch kubectl -n {{.NS}} get nodes,pod,svc,pvc,ingress
- watch-osh: watch kubectl -n {{.NS}} get nodes,pod,svc,pvc,route
- watch-cert: watch kubectl -n {{.NS}} get ingress,ClusterIssuers,Certificates,CertificateRequests,Orders,Challenges
- watch-pod: watch kubectl -n {{.NS}} get po,job --no-headers
+ env:
+ desc: print the current environment variables
+ cmds:
+ - env
- logs: >
- rm -f nuvolaris-operator.log ;
- kubectl -n nuvolaris logs pod/nuvolaris-operator -f | tee nuvolaris-operator.log
+ watch:
+ desc: watch nodes, pods, services, pvcs and ingresses in the namespace
+ cmds:
+ - watch kubectl -n {{.NS}} get nodes,pod,svc,pvc,ingress
- cli:
+ watch-osh:
+ desc: watch nodes, pods, services, pvcs and routes (OpenShift) in the namespace
+ cmds:
+ - watch kubectl -n {{.NS}} get nodes,pod,svc,pvc,route
+
+ watch-cert:
+ desc: watch cert-manager resources (ingress, issuers, certificates, orders, challenges)
+ cmds:
+ - watch kubectl -n {{.NS}} get ingress,ClusterIssuers,Certificates,CertificateRequests,Orders,Challenges
+
+ watch-pod:
+ desc: watch pods and jobs in the namespace
+ cmds:
+ - watch kubectl -n {{.NS}} get po,job --no-headers
+
+ logs:
+ desc: tail the operator logs to nuvolaris-operator.log
+ cmds:
+ - >
+ rm -f nuvolaris-operator.log ;
+ kubectl -n nuvolaris logs pod/nuvolaris-operator -f | tee nuvolaris-operator.log
+
+ cli:
+ desc: run the interactive development CLI
+ cmds:
- task: d:cli
run:
+ desc: prepare system actions, apply permissions and run the operator locally
+ cmds:
- task: sys:prepare
- task: t:permission
- task: d:run
-
+
irun:
- - task: instance
- - task: d:run
+ desc: deploy an instance and run the operator locally
+ cmds:
+ - task: instance
+ - task: d:run
permission:
+ desc: apply the nuvolaris permissions
+ cmds:
- task: t:permission
operator:
+ desc: deploy the operator
+ cmds:
- task: t:operator
instance:
+ desc: deploy an openwhisk instance
+ cmds:
- task: t:instance
instance-wfx:
+ desc: deploy an openwhisk instance with workflow extension
+ cmds:
- task: d:instance-wfx
instance-and-log:
+ desc: deploy an instance and follow the operator logs
+ cmds:
- task: d:instance
- kubectl -n nuvolaris logs pod/nuvolaris-operator -f
- mongo:
- - task: t:mongo
+ mongo:
+ desc: run the mongo integration test
+ cmds:
+ - task: t:mongo
- minio:
+ minio:
+ desc: run the minio integration test
+ cmds:
- task: t:minio
postgres:
- - task: t:postgres
+ desc: run the postgres integration test
+ cmds:
+ - task: t:postgres
minimal:
+ desc: deploy operator and a minimal instance, then run hello test
+ cmds:
#- task: permission
- task: operator
- task instance WHISK=minimal
@@ -144,6 +192,8 @@
- task: hello
all:
+ desc: full end-to-end setup and test of all services
+ cmds:
- task: permission
- task: operator
- task: instance
@@ -155,28 +205,41 @@
- task: postgres
config:
+ desc: configure ops wsk properties for the deployed instance
+ cmds:
- task: t:config
- hello:
+ hello:
+ desc: run the hello world test action
+ cmds:
- task: t:hello
- redis:
+ redis:
+ desc: run the redis integration test
+ cmds:
- task: t:redis
- workflow:
+ workflow:
+ desc: apply the workflow test manifest
+ cmds:
- kubectl -n nuvolaris apply -f tests/workflow-test.yaml
defin:
+ desc: remove finalizers to force-delete the wsk controller resource
+ cmds:
- task: d:defin
# old., to be removed
image-tag:
- - git tag -d $(git tag)
+ desc: "[deprecated] generate and force a new git tag based on the current time"
+ cmds:
+ - git tag -d $(git tag)
- git tag -f {{.P}}{{.BASETAG}}.$(date +%y%m%d%H%M)
- env PAGER= git tag
# configure env
configure-env:
+ desc: check that .env exists (copy from .env.dist if missing)
cmds:
- |
if ! test -e .env
@@ -184,7 +247,8 @@
fi
# update configuration files from openwhisk source
- update-files-from-openwhisk:
+ update-files-from-openwhisk:
+ desc: update configuration files copied from the openwhisk source repo
ignore_error: true
cmds:
- |
@@ -201,11 +265,13 @@
# generate ssh keys
ssh-key:
+ desc: generate an ssh key pair for cluster access if missing
cmds:
- test -f clusters/id_rsa || ssh-keygen -b 2048 -t rsa -f clusters/id_rsa -q -N ""
- ssh-keygen -y -f clusters/id_rsa >clusters/id_rsa.pub
clean:
+ desc: delete all deployed resources in the nuvolaris namespace
cmds:
- cmd: kubectl -n nuvolaris delete wsku --all
ignore_error: true
@@ -230,6 +296,7 @@
utest:
+ desc: run python doctests for nuvolaris modules (T=filter)
cmds:
- |
for test in nuvolaris/{{.T}}*.py
@@ -238,9 +305,13 @@
done
silent: true
- iclean: rm -f deploy/*/kustomization.yaml deploy/*/__* deploy/*/*_generated.yaml
+ iclean:
+ desc: remove generated kustomization and generated deploy files
+ cmds:
+ - rm -f deploy/*/kustomization.yaml deploy/*/__* deploy/*/*_generated.yaml
itest:
+ desc: run ipython integration tests against the current kube (T=filter)
cmds:
- task: iclean
- |
@@ -265,6 +336,7 @@
silent: true
dtest:
+ desc: deploy permissions, operator and instance, then run the actions tests
cmds:
- task: permission
- task: operator
@@ -272,24 +344,31 @@
- task: actions
actions:
+ desc: run config, hello, redis and echo action tests
+ cmds:
- task: t:config
- task: t:hello
- task: t:redis
- task: t:echo
test:
+ desc: run the full test suite (clean, unit, integration, deploy tests)
+ cmds:
- task: clean
- task: utest
- task: itest
- task: dtest
- debug:
+ debug:
+ desc: set up an ipython debug profile and run the ui integration test
+ cmds:
- poetry run ipython profile create
- cp test_profile.ipy ~/.ipython/profile_default/startup/
- task: uitest
- all-kubes:
- cmds:
+ all-kubes:
+ desc: run a task against every configured kube (usage - task all-kubes -- <target>)
+ cmds:
- |-
if test -z "{{.CLI_ARGS}}"
then echo 'use "task all-kubes -- <target> runs the target against all the available kubes'
@@ -301,11 +380,13 @@
fi
silent: true
- kube-test:
+ kube-test:
+ desc: run dtest quietly, discarding all output
cmds:
- task dtest 2>/dev/null >/dev/null
- use:
+ use:
+ desc: "list available kubeconfigs, or switch to the one matching N"
cmds:
- |-
if test -z "{{.N}}"
@@ -320,15 +401,23 @@
silent: true
build-and-load:
+ desc: build the operator image and load it into the local kind cluster
+ cmds:
- task: b:build-and-load
build-and-push:
+ desc: build the operator image and push it to the registry
+ cmds:
- task: b:build-and-push
buildx-and-push:
+ desc: build the operator image (multi-arch) and push it to the registry
+ cmds:
- task: b:buildx-and-push
docker-login:
+ desc: log in to the docker registry
+ cmds:
- task: b:docker-login
# openserverless-operator section
@@ -398,15 +487,35 @@
shell:
+ desc: open an interactive shell in the given pod (POD=name)
+ cmds:
- kubectl -n nuvolaris exec --stdin --tty {{.POD}} -- /bin/bash
-
- 1: task use N=1
- 2: task use N=2
- 3: task use N=3
- 4: task use N=4
- 5: task use N=5
- 6: task use N=6
- 7: task use N=7
- 8: task use N=8
- 9: task use N=9
+
+ 1:
+ cmds:
+ - task use N=1
+ 2:
+ cmds:
+ - task use N=2
+ 3:
+ cmds:
+ - task use N=3
+ 4:
+ cmds:
+ - task use N=4
+ 5:
+ cmds:
+ - task use N=5
+ 6:
+ cmds:
+ - task use N=6
+ 7:
+ cmds:
+ - task use N=7
+ 8:
+ cmds:
+ - task use N=8
+ 9:
+ cmds:
+ - task use N=9
diff --git a/TaskfileDev.yml b/TaskfileDev.yml
index c7dc981..d5c7503 100644
--- a/TaskfileDev.yml
+++ b/TaskfileDev.yml
@@ -68,6 +68,8 @@
COUCHDB_SERVICE_HOST: "localhost"
MINIO_API_HOST: "localhost"
OW_CONTROLLER_HOST : "localhost"
+ VIRTUAL_ENV:
+ sh: poetry env info --path 2>/dev/null || echo ""
OW_CONTROLLER_PORT : 3233
INVOKER_IMAGE: "{{.INVOKER_IMAGE}}"
INVOKER_TAG: "{{.INVOKER_TAG}}"
diff --git a/actions/devel/download/nuvolaris/couchdb_util.py b/actions/devel/download/nuvolaris/couchdb_util.py
index 7a18899..b518a2d 100644
--- a/actions/devel/download/nuvolaris/couchdb_util.py
+++ b/actions/devel/download/nuvolaris/couchdb_util.py
@@ -40,7 +40,7 @@
session = req.Session()
while delta < max_seconds:
try:
- r = session.get(f"{self.db_url}/_utils", timeout=5)
+ r = session.get(f"{self.db_url}/_utils/", timeout=5)
logging.info(f"CouchDB.wait_db_ready() got response code = {r.status_code}")
if r.status_code == 200:
return True
diff --git a/actions/devel/ferretdb/nuvolaris/couchdb_util.py b/actions/devel/ferretdb/nuvolaris/couchdb_util.py
index 7a18899..b518a2d 100644
--- a/actions/devel/ferretdb/nuvolaris/couchdb_util.py
+++ b/actions/devel/ferretdb/nuvolaris/couchdb_util.py
@@ -40,7 +40,7 @@
session = req.Session()
while delta < max_seconds:
try:
- r = session.get(f"{self.db_url}/_utils", timeout=5)
+ r = session.get(f"{self.db_url}/_utils/", timeout=5)
logging.info(f"CouchDB.wait_db_ready() got response code = {r.status_code}")
if r.status_code == 200:
return True
diff --git a/actions/devel/minio/nuvolaris/couchdb_util.py b/actions/devel/minio/nuvolaris/couchdb_util.py
index 7a18899..b518a2d 100644
--- a/actions/devel/minio/nuvolaris/couchdb_util.py
+++ b/actions/devel/minio/nuvolaris/couchdb_util.py
@@ -40,7 +40,7 @@
session = req.Session()
while delta < max_seconds:
try:
- r = session.get(f"{self.db_url}/_utils", timeout=5)
+ r = session.get(f"{self.db_url}/_utils/", timeout=5)
logging.info(f"CouchDB.wait_db_ready() got response code = {r.status_code}")
if r.status_code == 200:
return True
diff --git a/actions/devel/psql/nuvolaris/couchdb_util.py b/actions/devel/psql/nuvolaris/couchdb_util.py
index 7a18899..b518a2d 100644
--- a/actions/devel/psql/nuvolaris/couchdb_util.py
+++ b/actions/devel/psql/nuvolaris/couchdb_util.py
@@ -40,7 +40,7 @@
session = req.Session()
while delta < max_seconds:
try:
- r = session.get(f"{self.db_url}/_utils", timeout=5)
+ r = session.get(f"{self.db_url}/_utils/", timeout=5)
logging.info(f"CouchDB.wait_db_ready() got response code = {r.status_code}")
if r.status_code == 200:
return True
diff --git a/actions/devel/redis/nuvolaris/couchdb_util.py b/actions/devel/redis/nuvolaris/couchdb_util.py
index 7a18899..b518a2d 100644
--- a/actions/devel/redis/nuvolaris/couchdb_util.py
+++ b/actions/devel/redis/nuvolaris/couchdb_util.py
@@ -40,7 +40,7 @@
session = req.Session()
while delta < max_seconds:
try:
- r = session.get(f"{self.db_url}/_utils", timeout=5)
+ r = session.get(f"{self.db_url}/_utils/", timeout=5)
logging.info(f"CouchDB.wait_db_ready() got response code = {r.status_code}")
if r.status_code == 200:
return True
diff --git a/actions/devel/upload/nuvolaris/couchdb_util.py b/actions/devel/upload/nuvolaris/couchdb_util.py
index 7a18899..b518a2d 100644
--- a/actions/devel/upload/nuvolaris/couchdb_util.py
+++ b/actions/devel/upload/nuvolaris/couchdb_util.py
@@ -40,7 +40,7 @@
session = req.Session()
while delta < max_seconds:
try:
- r = session.get(f"{self.db_url}/_utils", timeout=5)
+ r = session.get(f"{self.db_url}/_utils/", timeout=5)
logging.info(f"CouchDB.wait_db_ready() got response code = {r.status_code}")
if r.status_code == 200:
return True
diff --git a/deploy/nuvolaris-permissions/whisk-user-crd.yaml b/deploy/nuvolaris-permissions/whisk-user-crd.yaml
index 2fc7402..878805f 100644
--- a/deploy/nuvolaris-permissions/whisk-user-crd.yaml
+++ b/deploy/nuvolaris-permissions/whisk-user-crd.yaml
@@ -69,7 +69,10 @@
type: string
namespace:
description: ow namespace assigned to the user
- type: string
+ type: string
+ x-kubernetes-validations:
+ - rule: "self.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$')"
+ message: "Invalid namespace name"
auth:
description: ow auth used to authenticate the user
type: string
@@ -87,6 +90,9 @@
prefix:
description: redis key prefixused to configure a user custom made ACL
type: string
+ x-kubernetes-validations:
+ - rule: "self.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$')"
+ message: "Invalid redis username name"
password:
description: user redis password
type: string
diff --git a/nuvolaris/couchdb.py b/nuvolaris/couchdb.py
index e6ebf6a..6a38cf7 100644
--- a/nuvolaris/couchdb.py
+++ b/nuvolaris/couchdb.py
@@ -43,8 +43,8 @@
user = f"db_username={u}"
pasw = f"db_password={p}"
- img = cfg.get('operator.image') or "missing-operator-image"
- tag = cfg.get('operator.tag') or "missing-operator-tag"
+ img = cfg.get('operator.image', 'OPERATOR_IMAGE', "missing-operator-image")
+ tag = cfg.get('operator.tag', 'OPERATOR_TAG', "missing-operator-tag")
image = f"{img}:{tag}"
container_image = runtime in ['openshift'] and "ghcr.io/nuvolaris/couchdb:2.3.1-nuvolaris.23101915" or "apache/couchdb:2.3"
diff --git a/nuvolaris/couchdb_util.py b/nuvolaris/couchdb_util.py
index 7a18899..b518a2d 100644
--- a/nuvolaris/couchdb_util.py
+++ b/nuvolaris/couchdb_util.py
@@ -40,7 +40,7 @@
session = req.Session()
while delta < max_seconds:
try:
- r = session.get(f"{self.db_url}/_utils", timeout=5)
+ r = session.get(f"{self.db_url}/_utils/", timeout=5)
logging.info(f"CouchDB.wait_db_ready() got response code = {r.status_code}")
if r.status_code == 200:
return True
diff --git a/nuvolaris/cronjob.py b/nuvolaris/cronjob.py
index a0529d3..c02f375 100644
--- a/nuvolaris/cronjob.py
+++ b/nuvolaris/cronjob.py
@@ -24,8 +24,8 @@
def create(owner=None):
logging.info("creating cron")
- img = cfg.get('operator.image') or "missing-operator-image"
- tag = cfg.get('operator.tag') or "missing-operator-tag"
+ img = cfg.get("operator.image","OPERATOR_IMAGE", "ghcr.io/nuvolaris/openwhisk-controller")
+ tag = cfg.get("operator.tag","OPERATOR_TAG", "3.1.0-mastrogpt.2402101445")
image = f"{img}:{tag}"
logging.info(f"cron using image {image}")
diff --git a/nuvolaris/enterprise_util.py b/nuvolaris/enterprise_util.py
index fd0d0bb..bd86f4f 100644
--- a/nuvolaris/enterprise_util.py
+++ b/nuvolaris/enterprise_util.py
@@ -43,9 +43,10 @@
img_data = util.parse_image(invoker_image)
data['invoker_image'] = img_data["image"]
data['invoker_tag'] = img_data["tag"]
- else:
- data['invoker_image'] = cfg.get("invoker.image") or "ghcr.io/nuvolaris/openwhisk-invoker"
- data['invoker_tag'] = cfg.get("invoker.tag") or "3.1.0-mastrogpt.2402101445"
+ else:
+ #ghcr.io/nuvolaris/openwhisk-invoker:3.1.0-mastrogpt.2402101445
+ data['invoker_image'] = cfg.get("invoker.image","INVOKER_IMAGE", "ghcr.io/nuvolaris/openwhisk-invoker")
+ data['invoker_tag'] = cfg.get("invoker.tag", "INVOKER_TAG", "3.1.0-mastrogpt.2402101445")
def getEnterpriseControllerConfigData():
data = {
diff --git a/nuvolaris/ingress_data.py b/nuvolaris/ingress_data.py
index 1ac7e3d..c564a97 100644
--- a/nuvolaris/ingress_data.py
+++ b/nuvolaris/ingress_data.py
@@ -110,6 +110,8 @@
"""
uses the given template to render a final ingress template and returns the path to the template
"""
+ if not util.validate_namespace(namespace):
+ raise ValueError(f"Invalid namespace {namespace}")
logging.info(f"*** Rendering ingress template using host {self._data['hostname']} endpoint for {self._data['ingress_name']} via template {tpl}")
out = f"/tmp/__{namespace}_{tpl}"
file = ntp.spool_template(tpl, out, self._data)
@@ -118,7 +120,9 @@
def render_traefik_middleware_template(self, namespace,tpl="traefik-middleware-tpl.yaml"):
"""
uses the given template policy to render a final ingress template. By default renders an addPrefix middleware.
- """
+ """
+ if not util.validate_namespace(namespace):
+ raise ValueError(f"Invalid namespace {namespace}")
logging.info(f"*** Rendering traefik middleware template using host {self._data['hostname']} endpoint for {self._data['ingress_name']} via template {tpl}")
out = f"/tmp/__{namespace}_{tpl}"
file = ntp.spool_template(tpl, out, self._data)
diff --git a/nuvolaris/invoker.py b/nuvolaris/invoker.py
index 92c673f..0ae65dd 100644
--- a/nuvolaris/invoker.py
+++ b/nuvolaris/invoker.py
@@ -39,8 +39,8 @@
if(data['affinity'] or data['tolerations']):
tplp.append("affinity-tolerance-sts-invoker-attach.yaml")
- config = kus.image(whisk_image, newTag=whisk_tag)
- config += kus.patchTemplates("openwhisk-invoker", tplp, data)
+ logging.info(f"using invoker image {whisk_image}:{whisk_tag}")
+ config = kus.patchTemplates("openwhisk-invoker", tplp, data)
spec = kus.kustom_list("openwhisk-invoker", config, templates=[], data=data)
if owner:
diff --git a/nuvolaris/mongodb.py b/nuvolaris/mongodb.py
index 50bf5c8..58c5ea9 100644
--- a/nuvolaris/mongodb.py
+++ b/nuvolaris/mongodb.py
@@ -114,12 +114,16 @@
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}")
diff --git a/nuvolaris/openwhisk_enterprise.py b/nuvolaris/openwhisk_enterprise.py
index 6fb6a19..1c4d574 100644
--- a/nuvolaris/openwhisk_enterprise.py
+++ b/nuvolaris/openwhisk_enterprise.py
@@ -36,8 +36,8 @@
if(data['affinity'] or data['tolerations']):
tplp.append("affinity-tolerance-sts-core-attach.yaml")
- config = kus.image(whisk_image, newTag=whisk_tag)
- config += kus.patchTemplates("openwhisk-enterprise", tplp, data)
+ logging.info(f"using controller image {whisk_image}:{whisk_tag}")
+ config = kus.patchTemplates("openwhisk-enterprise", tplp, data)
spec = kus.kustom_list("openwhisk-enterprise", config, templates=[], data=data)
if owner:
diff --git a/nuvolaris/openwhisk_standalone.py b/nuvolaris/openwhisk_standalone.py
index f44bc3d..1d53d43 100644
--- a/nuvolaris/openwhisk_standalone.py
+++ b/nuvolaris/openwhisk_standalone.py
@@ -31,13 +31,14 @@
whisk_image = data["controller_image"]
whisk_tag = data["controller_tag"]
- config = kus.image(whisk_image, newTag=whisk_tag)
-
+
+ logging.info(f"using controller image {whisk_image}:{whisk_tag}")
+
tplp = ["standalone-sts.yaml"]
if(data['affinity'] or data['tolerations']):
tplp.append("affinity-tolerance-sts-core-attach.yaml")
- config += kus.patchTemplates("openwhisk-standalone", tplp, data)
+ config = kus.patchTemplates("openwhisk-standalone", tplp, data)
spec = kus.kustom_list("openwhisk-standalone", config, templates=[], data=data)
if owner:
diff --git a/nuvolaris/postgres_operator.py b/nuvolaris/postgres_operator.py
index fb11659..7274e87 100644
--- a/nuvolaris/postgres_operator.py
+++ b/nuvolaris/postgres_operator.py
@@ -173,12 +173,16 @@
def render_postgres_script(namespace,template,data):
"""
uses the given template to render a sh script to execute via psql.
- """
+ """
+ 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_psql_command(pod_name,path_to_psql_script,path_to_pgpass,additional_psql_args=''):
+ if not os.path.exists(path_to_psql_script):
+ raise ValueError(f"invalid path script in exec_mongosh_command")
logging.info(f"passing script {path_to_psql_script} to pod {pod_name}")
res = kube.kubectl("cp",path_to_psql_script,f"{pod_name}:{path_to_psql_script}")
res = kube.kubectl("cp",path_to_pgpass,f"{pod_name}:/tmp/.pgpass")
diff --git a/nuvolaris/quota_checker_job.py b/nuvolaris/quota_checker_job.py
index 534d447..d67c552 100644
--- a/nuvolaris/quota_checker_job.py
+++ b/nuvolaris/quota_checker_job.py
@@ -24,8 +24,8 @@
def create(owner=None):
logging.info("creating quota cheker scheduled job")
- img = cfg.get('operator.image') or "missing-operator-image"
- tag = cfg.get('operator.tag') or "missing-operator-tag"
+ img = cfg.get('operator.image','OPERATOR_IMAGE') or "missing-operator-image"
+ tag = cfg.get('operator.tag','OPERATOR_TAG') or "missing-operator-tag"
image = f"{img}:{tag}"
logging.info("quota job using image %s", image)
diff --git a/nuvolaris/redis.py b/nuvolaris/redis.py
index f6ed2f8..fe51440 100644
--- a/nuvolaris/redis.py
+++ b/nuvolaris/redis.py
@@ -152,12 +152,19 @@
def render_redis_script(namespace,template,data):
"""
uses the given template to render a redis-cli script to be executed.
- """
+ """
+ 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_redis_command(pod_name,path_to_script):
+ if not os.path.exists(path_to_script):
+ raise ValueError(f"invalid path script in exec_redis_command")
+
+
logging.info(f"passing script {path_to_script} to pod {pod_name}")
res = kube.kubectl("cp",path_to_script,f"{pod_name}:{path_to_script}")
res = kube.kubectl("exec","-it",pod_name,"--","/bin/bash","-c",f"cat {path_to_script} | redis-cli")
diff --git a/nuvolaris/route_data.py b/nuvolaris/route_data.py
index 63a9b0b..7e6b0e8 100644
--- a/nuvolaris/route_data.py
+++ b/nuvolaris/route_data.py
@@ -81,7 +81,9 @@
logging.info(f"*** Rendering route template using host {self._data['hostname']} endpoint for {self._data['route_name']} via template {tpl}")
"""
uses the given template to render a final route template and returns the path to the template
- """
+ """
+ if not util.validate_namespace(namespace):
+ raise ValueError(f"Invalid namespace {namespace}")
out = f"/tmp/__{namespace}_{tpl}"
file = ntp.spool_template(tpl, out, self._data)
return os.path.abspath(file)
\ No newline at end of file
diff --git a/nuvolaris/secret_htpasswd_data.py b/nuvolaris/secret_htpasswd_data.py
index 7a636d4..821aa14 100644
--- a/nuvolaris/secret_htpasswd_data.py
+++ b/nuvolaris/secret_htpasswd_data.py
@@ -20,6 +20,7 @@
import os
import nuvolaris.kustomize as kus
import nuvolaris.template as ntp
+import nuvolaris.util as util
import bcrypt
import base64
@@ -59,6 +60,8 @@
"""
uses the given template to render a final htpassword secret template and returns the path to the template
"""
+ if not util.validate_namespace(namespace):
+ raise ValueError(f"Invalid namespace {namespace}")
logging.info(f"*** Rendering htpassword secret template with name {self._data['secret_name']} via template {tpl}")
out = f"/tmp/__{namespace}_{tpl}"
file = ntp.spool_template(tpl, out, self._data)
diff --git a/nuvolaris/secret_imagepull_data.py b/nuvolaris/secret_imagepull_data.py
index f842d13..03e9d13 100644
--- a/nuvolaris/secret_imagepull_data.py
+++ b/nuvolaris/secret_imagepull_data.py
@@ -20,6 +20,7 @@
import os
import nuvolaris.kustomize as kus
import nuvolaris.template as ntp
+import nuvolaris.util as util
import base64
class ImagePullSecretData:
@@ -65,6 +66,8 @@
"""
uses the given template to render a final ImagePull secret template and returns the path to the template
"""
+ if not util.validate_namespace(namespace):
+ raise ValueError(f"Invalid namespace {namespace}")
logging.info(f"*** Rendering ImagePull secret template with name {self._data['secret_name']} via template {tpl}")
out = f"/tmp/__{namespace}_{self._data['secret_name']}_{tpl}"
file = ntp.spool_template(tpl, out, self._data)
diff --git a/nuvolaris/templates/enterprise-sts.yaml b/nuvolaris/templates/enterprise-sts.yaml
index db8320a..3511f81 100644
--- a/nuvolaris/templates/enterprise-sts.yaml
+++ b/nuvolaris/templates/enterprise-sts.yaml
@@ -48,7 +48,7 @@
containers:
- name: controller
imagePullPolicy: "IfNotPresent"
- image: "ghcr.io/nuvolaris/openwhisk-controller:0.3.0-morpheus.22122609"
+ image: "{{controller_image}}:{{controller_tag}}"
command: ["/bin/bash", "-c", "/init.sh `hostname | awk -F '-' '{print $NF}'`"]
ports:
- name: controller
diff --git a/nuvolaris/templates/invoker-sts.yaml b/nuvolaris/templates/invoker-sts.yaml
index 1f27bcf..e84a38d 100644
--- a/nuvolaris/templates/invoker-sts.yaml
+++ b/nuvolaris/templates/invoker-sts.yaml
@@ -47,7 +47,7 @@
command: ['sh', '-c', "until nslookup kafka.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for kafka; sleep 2; done"]
containers:
- name: invoker
- image: "ghcr.io/nuvolaris/openwhisk-invoker:0.3.0-morpheus.22122609"
+ image: "{{invoker_image}}:{{invoker_tag}}"
imagePullPolicy: "IfNotPresent"
command: [ "/bin/bash", "-c", "/init.sh --uniqueName $INVOKER_NAME" ]
#command: ["sleep", "inf"]
diff --git a/nuvolaris/templates/standalone-sts.yaml b/nuvolaris/templates/standalone-sts.yaml
index 2ee9da8..611f502 100644
--- a/nuvolaris/templates/standalone-sts.yaml
+++ b/nuvolaris/templates/standalone-sts.yaml
@@ -45,7 +45,7 @@
containers:
- name: controller
imagePullPolicy: "IfNotPresent"
- image: "ghcr.io/nuvolaris/openwhisk-controller:0.3.0-morpheus.22122609"
+ image: "{{controller_image}}:{{controller_tag}}"
command: ["/bin/bash", "-c", "/init.sh `hostname | awk -F '-' '{print $NF}'`"]
ports:
- name: controller
diff --git a/nuvolaris/util.py b/nuvolaris/util.py
index b616851..34a1bb2 100644
--- a/nuvolaris/util.py
+++ b/nuvolaris/util.py
@@ -22,6 +22,7 @@
import time
import uuid
import os
+import re
from base64 import b64decode, b64encode
from typing import List, Union
from urllib.parse import urlparse
@@ -345,8 +346,8 @@
data['controller_image'] = img_data["image"]
data['controller_tag'] = img_data["tag"]
else:
- data['controller_image'] = cfg.get("controller.image") or "ghcr.io/nuvolaris/openwhisk-controller"
- data['controller_tag'] = cfg.get("controller.tag") or "3.1.0-mastrogpt.2402101445"
+ data['controller_image'] = cfg.get("controller.image","CONTROLLER_IMAGE", "ghcr.io/nuvolaris/openwhisk-controller")
+ data['controller_tag'] = cfg.get("controller.tag","CONTROLLER_TAG", "3.1.0-mastrogpt.2402101445")
# return configuration parameters for the standalone controller
def get_standalone_config_data():
@@ -386,6 +387,17 @@
standalone_affinity_tolerations_data(data)
return data
+def validate_namespace(namespace: str) -> bool:
+ """
+ >>> import nuvolaris.util as util
+ >>> util.validate_namespace("demouser")
+ True
+ >>> util.validate_namespace('x;id;#')
+ False
+ """
+ NAMESPACE_RE = re.compile(r"^[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?$")
+ return bool(NAMESPACE_RE.fullmatch(namespace))
+
def validate_ow_auth(auth):
"""
>>> import nuvolaris.testutil as tutil
diff --git a/nuvolaris/zookeeper.py b/nuvolaris/zookeeper.py
index b7edb6a..0ba5197 100644
--- a/nuvolaris/zookeeper.py
+++ b/nuvolaris/zookeeper.py
@@ -39,7 +39,7 @@
return None
def create(owner=None):
- logging.info(f"*** configuring zookeeper")
+ logging.info("*** configuring zookeeper")
data = cfg_util.get_zookeeper_config_data()
diff --git a/run.sh b/run.sh
index 8a40774..38f9f11 100755
--- a/run.sh
+++ b/run.sh
@@ -26,7 +26,11 @@
cd ..
fi
# start the operator if possible
+KOPF="${VIRTUAL_ENV:-/home/nuvolaris/.venv}/bin/kopf"
if kubectl -n nuvolaris get cm/config
-then exec /home/nuvolaris/.venv/bin/kopf run -n nuvolaris -m nuvolaris nuvolaris/main.py nuvolaris/user_handlers.py nuvolaris/workflows.py "$@"
+then if test -x "$KOPF"
+ then exec "$KOPF" run -n nuvolaris -m nuvolaris nuvolaris/main.py nuvolaris/user_handlers.py nuvolaris/workflows.py "$@"
+ 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."
fi
diff --git a/tests/kind/milvus_standalone_slim_test.ipy b/tests/kind/milvus_standalone_slim_test.ipy
index edd404a..2480dbe 100644
--- a/tests/kind/milvus_standalone_slim_test.ipy
+++ b/tests/kind/milvus_standalone_slim_test.ipy
@@ -22,6 +22,7 @@
import nuvolaris.config as cfg
import nuvolaris.milvus_standalone as milvus
import nuvolaris.minio_deploy as minio
+import nuvolaris.seaweedfs_deploy as seaweedfs
import nuvolaris.etcd as etcd
import nuvolaris.testutil as tu
from nuvolaris.milvus_simple_client import MilvusSimpleClient as MilvusClient
@@ -31,21 +32,37 @@
#tu.enable_debug_logging()
### Initial cleanup
-!kubectl -n nuvolaris delete all --all
-!kubectl -n nuvolaris delete pvc --all
+tu.run_proc("kubectl -n nuvolaris delete all --all")
+tu.run_proc("kubectl -n nuvolaris delete pvc --all")
# test
assert(cfg.configure(tu.load_sample_config("kind/whisk-slim")))
assert(cfg.detect_storage()["nuvolaris.storageclass"])
assert(cfg.get("nuvolaris.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)
+
# for this test minioClient and Milvus should see this env variable
-os.environ['MINIO_API_HOST']='localhost'
+if is_minio:
+ os.environ['MINIO_API_HOST']='localhost'
+if is_seaweedfs:
+ os.environ['SEAWEEDFS_API_HOST']='localhost'
+ os.environ['SEAWEEDFS_API_PORT']='9090'
+
os.environ['MILVUS_API_HOST']='localhost'
assert(etcd.create())
-assert(minio.create())
+
+if is_minio:
+ assert(minio.create())
+if is_seaweedfs:
+ assert(seaweedfs.create())
+
assert(milvus.create())
# generate some user config
@@ -86,7 +103,8 @@
# test client
client = MilvusClient(uri=f'http://localhost:{port}',token=token, db_name=database)
client.create_collection("testcollection", dimension=128)
-assert("testcollection" in client.list_collections())
+collections = client.list_collections() or []
+assert("testcollection" in collections)
client.drop_collection("testcollection")
# test user deletion
@@ -94,9 +112,12 @@
assert(milvus.delete())
assert(etcd.delete())
-assert(minio.delete())
+if is_minio:
+ assert(minio.delete())
+if is_seaweedfs:
+ assert(seaweedfs.delete())
# final cleanup
-!kubectl -n nuvolaris delete all --all
+tu.run_proc("kubectl -n nuvolaris delete all --all")
# etcd pvc in particular
-!kubectl -n nuvolaris delete pvc --all
\ No newline at end of file
+tu.run_proc("kubectl -n nuvolaris delete pvc --all")
diff --git a/tests/kind/milvus_standalone_test.ipy b/tests/kind/milvus_standalone_test.ipy
index 26faeae..4011c4e 100644
--- a/tests/kind/milvus_standalone_test.ipy
+++ b/tests/kind/milvus_standalone_test.ipy
@@ -23,6 +23,7 @@
import nuvolaris.etcd as etcd
import nuvolaris.milvus_standalone as milvus
import nuvolaris.minio_deploy as minio
+import nuvolaris.seaweedfs_deploy as seaweedfs
import nuvolaris.testutil as tu
from nuvolaris.milvus_simple_client import MilvusSimpleClient as MilvusClient
from nuvolaris.user_config import UserConfig
@@ -39,13 +40,27 @@
assert(cfg.configure(tu.load_sample_config()))
assert(cfg.detect_storage()["nuvolaris.storageclass"])
assert(cfg.get("nuvolaris.slim") == None or cfg.get("nuvolaris.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)
+
# for this test minioClient and Milvus should see this env variable
-os.environ['MINIO_API_HOST']='localhost'
+# 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'
os.environ['MILVUS_API_HOST']='localhost'
assert(etcd.create())
-assert(minio.create())
+if is_minio:
+ assert(minio.create())
+if is_seaweedfs:
+ assert(seaweedfs.create())
assert(milvus.create())
# generate some user config
@@ -85,7 +100,9 @@
# test client
client = MilvusClient(uri=f'http://localhost:{port}',token=token, db_name=database)
client.create_collection("testcollection", dimension=128)
-assert("testcollection" in client.list_collections())
+collections = client.list_collections() or []
+assert("testcollection" in collections)
+
client.drop_collection("testcollection")
# test user deletion
@@ -93,7 +110,10 @@
assert(milvus.delete())
assert(etcd.delete())
-assert(minio.delete())
+if is_minio:
+ assert(minio.delete())
+if is_seaweedfs:
+ assert(seaweedfs.delete())
tu.run_proc("kubectl -n nuvolaris delete all --all")
tu.run_proc("kubectl -n nuvolaris delete pvc --all")
diff --git a/tests/kind/nuvolaris_subject_test.ipy b/tests/kind/nuvolaris_subject_test.ipy
index 6436dc8..242b67d 100644
--- a/tests/kind/nuvolaris_subject_test.ipy
+++ b/tests/kind/nuvolaris_subject_test.ipy
@@ -16,23 +16,25 @@
# under the License.
#
-!kubectl -n nuvolaris delete all --all
-!kubectl -n nuvolaris delete pvc --all
-
import nuvolaris.couchdb as cdb
import nuvolaris.couchdb_util as cdbu
import nuvolaris.testutil as tu
import nuvolaris.config as cfg
import nuvolaris.kube as kube
+import nuvolaris.testutil as tu
import time
from kopf.testing import KopfRunner
+tu.run_proc("kubectl -n nuvolaris delete all --all")
+tu.run_proc("kubectl -n nuvolaris delete pvc --all")
+
+
assert(cfg.configure(tu.load_sample_config()))
assert(cfg.detect_labels()["nuvolaris.kube"] == "kind")
assert(cfg.detect_storage()["nuvolaris.storageclass"] == "standard")
assert(cfg.put("couchdb.host", "localhost"))
-!kubectl apply -f tests/kind/whisk.yaml
+tu.run_proc("kubectl apply -f tests/kind/whisk.yaml")
wsk = kube.get("wsk/controller")
cdb.create(wsk)
@@ -51,17 +53,17 @@
with KopfRunner(['run', '-A', '--verbose', 'nuvolaris/user_handlers.py']) as runner:
# do something while the operator is running.
- !kubectl apply -f tests/whisk-user.yaml
+ tu.run_proc("kubectl apply -f tests/whisk-user.yaml")
time.sleep(1) # give it some time to react and to sleep and to retry
wsku = kube.get("wsku/franztt")
assert(wsku['spec'])
- !kubectl delete -f tests/whisk-user.yaml
+ tu.run_proc("kubectl delete -f tests/whisk-user.yaml")
time.sleep(1) # give it some time to react
assert runner.exit_code == 0
assert runner.exception is None
-!kubectl -n nuvolaris delete all --all
-!kubectl -n nuvolaris delete pvc --all
+tu.run_proc("kubectl -n nuvolaris delete all --all")
+tu.run_proc("kubectl -n nuvolaris delete pvc --all")
diff --git a/tests/kind/userdb_util_test.ipy b/tests/kind/userdb_util_test.ipy
index ec3b04e..84d4272 100644
--- a/tests/kind/userdb_util_test.ipy
+++ b/tests/kind/userdb_util_test.ipy
@@ -15,10 +15,6 @@
# specific language governing permissions and limitations
# under the License.
#
-
-!kubectl -n nuvolaris delete all --all
-!kubectl -n nuvolaris delete pvc --all
-
import json
import nuvolaris.config as cfg
@@ -30,13 +26,18 @@
import nuvolaris.user_metadata as user_metadata
import nuvolaris.userdb_util as userdb
import nuvolaris.bcrypt_util as bu
+import nuvolaris.testutil as tu
+
+tu.run_proc("kubectl -n nuvolaris delete all --all")
+tu.run_proc("kubectl -n nuvolaris delete pvc --all")
+
assert(cfg.configure(tu.load_sample_config()))
assert(cfg.detect_labels()["nuvolaris.kube"] == "kind")
assert(cfg.detect_storage()["nuvolaris.storageclass"] == "standard")
assert(cfg.put("couchdb.host", "localhost"))
-!kubectl apply -f tests/kind/whisk.yaml
+tu.run_proc("kubectl apply -f tests/kind/whisk.yaml")
wsk = kube.get("wsk/controller")
cdb.create(wsk)
@@ -49,7 +50,8 @@
assert(cdb.init_users_metadata(db))
# test user metadata creation
-!kubectl apply -f tests/kind/whisk-user.yaml
+tu.run_proc("kubectl apply -f tests/kind/whisk-user.yaml")
+
wsku = kube.get("wsku/franztt")
ucfg = user_config.UserConfig(wsku['spec'])
metadata = user_metadata.UserMetadata(ucfg)
@@ -64,7 +66,7 @@
# test password verification
doc = docs[0]
-assert(bu.verify_password(ucfg.get('password'),doc['password']))
+assert(bu.verify_password(ucfg.get('password') or '',doc['password']))
# test password change
new_password = 'test123'
@@ -83,6 +85,6 @@
assert(len(docs) == 0)
# cleanup
-!kubectl -n nuvolaris delete all --all
-!kubectl -n nuvolaris delete pvc --all
+tu.run_proc("kubectl -n nuvolaris delete all --all")
+tu.run_proc("kubectl -n nuvolaris delete pvc --all")
diff --git a/tests/kind/whisk-minimal.yaml b/tests/kind/whisk-minimal.yaml
index c3fc351..6b8afcc 100644
--- a/tests/kind/whisk-minimal.yaml
+++ b/tests/kind/whisk-minimal.yaml
@@ -25,11 +25,13 @@
# start openwhisk controller
openwhisk: true
# start openwhisk invoker
- invoker: false
+ invoker: true
# start couchdb
couchdb: true
# start kafka
- kafka: false
+ kafka: true
+ # zookeeper enabled or not
+ zookeeper: true
# start mongodb
mongodb: false
# start redis
diff --git a/tests/kind/whisk-slim.yaml b/tests/kind/whisk-slim.yaml
index f6f3f4a..8357bb4 100644
--- a/tests/kind/whisk-slim.yaml
+++ b/tests/kind/whisk-slim.yaml
@@ -41,19 +41,19 @@
# tls enabled or not
tls: false
# minio enabled or not
- minio: true
+ minio: false
# minio static enabled or not
static: true
# postgres enabled or not
postgres: true
# etcd enabled or not
- etcd: true
+ etcd: false
# milvus enabled or not
milvus: true
# registry enabled or not
registry: false
# seaweedfs enabled or not
- seaweedfs: false
+ seaweedfs: true
openwhisk:
namespaces:
whisk-system: 789c46b1-71f6-4ed5-8c54-816aa4f8c502:abczO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP