diff --git a/Taskfile.yml b/Taskfile.yml index 16d412ea..51341e5c 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -74,10 +74,13 @@ includes: 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 @@ tasks: 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 - - logs: > - rm -f nuvolaris-operator.log ; - kubectl -n nuvolaris logs pod/nuvolaris-operator -f | tee nuvolaris-operator.log - - cli: + env: + desc: print the current environment variables + cmds: + - env + + watch: + desc: watch nodes, pods, services, pvcs and ingresses in the namespace + cmds: + - watch kubectl -n {{.NS}} get nodes,pod,svc,pvc,ingress + + 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 @@ tasks: - 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 @@ tasks: - 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 @@ tasks: 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 @@ tasks: # 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 @@ tasks: utest: + desc: run python doctests for nuvolaris modules (T=filter) cmds: - | for test in nuvolaris/{{.T}}*.py @@ -238,9 +305,13 @@ tasks: 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 @@ tasks: silent: true dtest: + desc: deploy permissions, operator and instance, then run the actions tests cmds: - task: permission - task: operator @@ -272,24 +344,31 @@ tasks: - 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 -- ) + cmds: - |- if test -z "{{.CLI_ARGS}}" then echo 'use "task all-kubes -- runs the target against all the available kubes' @@ -301,11 +380,13 @@ tasks: 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 @@ tasks: 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 @@ tasks: 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 c7dc981e..d5c7503a 100644 --- a/TaskfileDev.yml +++ b/TaskfileDev.yml @@ -68,6 +68,8 @@ tasks: 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 7a18899b..b518a2dd 100644 --- a/actions/devel/download/nuvolaris/couchdb_util.py +++ b/actions/devel/download/nuvolaris/couchdb_util.py @@ -40,7 +40,7 @@ def wait_db_ready(self, max_seconds): 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 7a18899b..b518a2dd 100644 --- a/actions/devel/ferretdb/nuvolaris/couchdb_util.py +++ b/actions/devel/ferretdb/nuvolaris/couchdb_util.py @@ -40,7 +40,7 @@ def wait_db_ready(self, max_seconds): 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 7a18899b..b518a2dd 100644 --- a/actions/devel/minio/nuvolaris/couchdb_util.py +++ b/actions/devel/minio/nuvolaris/couchdb_util.py @@ -40,7 +40,7 @@ def wait_db_ready(self, max_seconds): 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 7a18899b..b518a2dd 100644 --- a/actions/devel/psql/nuvolaris/couchdb_util.py +++ b/actions/devel/psql/nuvolaris/couchdb_util.py @@ -40,7 +40,7 @@ def wait_db_ready(self, max_seconds): 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 7a18899b..b518a2dd 100644 --- a/actions/devel/redis/nuvolaris/couchdb_util.py +++ b/actions/devel/redis/nuvolaris/couchdb_util.py @@ -40,7 +40,7 @@ def wait_db_ready(self, max_seconds): 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 7a18899b..b518a2dd 100644 --- a/actions/devel/upload/nuvolaris/couchdb_util.py +++ b/actions/devel/upload/nuvolaris/couchdb_util.py @@ -40,7 +40,7 @@ def wait_db_ready(self, max_seconds): 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 2fc7402f..878805f7 100644 --- a/deploy/nuvolaris-permissions/whisk-user-crd.yaml +++ b/deploy/nuvolaris-permissions/whisk-user-crd.yaml @@ -69,7 +69,10 @@ spec: 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 @@ spec: 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 e6ebf6a8..6a38cf7e 100644 --- a/nuvolaris/couchdb.py +++ b/nuvolaris/couchdb.py @@ -43,8 +43,8 @@ def create(owner=None): 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 7a18899b..b518a2dd 100644 --- a/nuvolaris/couchdb_util.py +++ b/nuvolaris/couchdb_util.py @@ -40,7 +40,7 @@ def wait_db_ready(self, max_seconds): 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 a0529d35..c02f3751 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 fd0d0bb8..bd86f4f6 100644 --- a/nuvolaris/enterprise_util.py +++ b/nuvolaris/enterprise_util.py @@ -43,9 +43,10 @@ def get_invoker_image_data(data): 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 1ac7e3d7..c564a972 100644 --- a/nuvolaris/ingress_data.py +++ b/nuvolaris/ingress_data.py @@ -110,6 +110,8 @@ def render_template(self,namespace,tpl= "generic-ingress-tpl.yaml"): """ 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_template(self,namespace,tpl= "generic-ingress-tpl.yaml"): 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 92c673f1..0ae65dd4 100644 --- a/nuvolaris/invoker.py +++ b/nuvolaris/invoker.py @@ -39,8 +39,8 @@ def create(owner=None): 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 50bf5c83..58c5ea99 100644 --- a/nuvolaris/mongodb.py +++ b/nuvolaris/mongodb.py @@ -114,12 +114,16 @@ def init(): 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 6fb6a193..1c4d5744 100644 --- a/nuvolaris/openwhisk_enterprise.py +++ b/nuvolaris/openwhisk_enterprise.py @@ -36,8 +36,8 @@ def create(owner=None): 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 f44bc3dd..1d53d431 100644 --- a/nuvolaris/openwhisk_standalone.py +++ b/nuvolaris/openwhisk_standalone.py @@ -31,13 +31,14 @@ def create(owner=None): 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 fb11659a..7274e871 100644 --- a/nuvolaris/postgres_operator.py +++ b/nuvolaris/postgres_operator.py @@ -173,12 +173,16 @@ def _add_pdb_user_metadata(ucfg:UserConfig, user_metadata: UserMetadata): 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 534d447a..d67c5526 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 f6ed2f89..fe514405 100644 --- a/nuvolaris/redis.py +++ b/nuvolaris/redis.py @@ -152,12 +152,19 @@ def delete(owner=None): 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 63a9b0b7..7e6b0e84 100644 --- a/nuvolaris/route_data.py +++ b/nuvolaris/route_data.py @@ -81,7 +81,9 @@ def render_template(self,namespace,tpl= "generic-openshift-route-tpl.yaml"): 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 7a636d4b..821aa14f 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 @@ def render_template(self,namespace,tpl= "generic-secret-htpassword-tpl.yaml"): """ 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 f842d13e..03e9d137 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 @@ def render_template(self,namespace,tpl= "generic-secret-docker-tpl.yaml"): """ 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 db8320a7..3511f810 100644 --- a/nuvolaris/templates/enterprise-sts.yaml +++ b/nuvolaris/templates/enterprise-sts.yaml @@ -48,7 +48,7 @@ spec: 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 1f27bcf9..e84a38db 100644 --- a/nuvolaris/templates/invoker-sts.yaml +++ b/nuvolaris/templates/invoker-sts.yaml @@ -47,7 +47,7 @@ spec: 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 2ee9da87..611f5028 100644 --- a/nuvolaris/templates/standalone-sts.yaml +++ b/nuvolaris/templates/standalone-sts.yaml @@ -45,7 +45,7 @@ spec: 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 b6168516..34a1bb27 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 @@ def get_controller_image_data(data): 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 @@ def get_standalone_config_data(): 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 b7edb6af..0ba51979 100644 --- a/nuvolaris/zookeeper.py +++ b/nuvolaris/zookeeper.py @@ -39,7 +39,7 @@ def get_zookeeper_connect_data(): 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 8a40774d..38f9f114 100755 --- a/run.sh +++ b/run.sh @@ -26,7 +26,11 @@ then mkdir -p deploy 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 edd404ac..2480dbeb 100644 --- a/tests/kind/milvus_standalone_slim_test.ipy +++ b/tests/kind/milvus_standalone_slim_test.ipy @@ -22,6 +22,7 @@ import os 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 @@ from nuvolaris.user_metadata import UserMetadata #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 @@ assert port # 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_ow_milvus(ucfg)) 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 26faeae2..4011c4ed 100644 --- a/tests/kind/milvus_standalone_test.ipy +++ b/tests/kind/milvus_standalone_test.ipy @@ -23,6 +23,7 @@ import nuvolaris.config as cfg 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 @@ tu.run_proc("kubectl -n nuvolaris delete pvc --all") 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 @@ assert port # 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_ow_milvus(ucfg)) 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 6436dc8a..242b67d7 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 @@ assert(cdb.add_initial_subjects(db)) 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 ec3b04e9..84d4272f 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_config as user_config 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(db.configure_no_reduce_limit()) 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 @@ assert(len(docs) > 0) # 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 @@ docs = list(response['docs']) 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 c3fc351a..6b8afcce 100644 --- a/tests/kind/whisk-minimal.yaml +++ b/tests/kind/whisk-minimal.yaml @@ -25,11 +25,13 @@ spec: # 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 f6f3f4a9..8357bb40 100644 --- a/tests/kind/whisk-slim.yaml +++ b/tests/kind/whisk-slim.yaml @@ -41,19 +41,19 @@ spec: # 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