diff --git a/.licenserc.yaml b/.licenserc.yaml index 6d89a650..4e6b48d2 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -57,6 +57,7 @@ header: - 'olaris/**' - 'skip-test' - '.env.dist' + - '**/ow_pod_templates.yaml' dependency: diff --git a/Dockerfile b/Dockerfile index be256c9a..e460f0da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -69,6 +69,7 @@ ADD --chown=nuvolaris:nuvolaris deploy/etcd /home/nuvolaris/deploy/etcd ADD --chown=nuvolaris:nuvolaris deploy/milvus-operator /home/nuvolaris/deploy/milvus-operator ADD --chown=nuvolaris:nuvolaris deploy/milvus /home/nuvolaris/deploy/milvus ADD --chown=nuvolaris:nuvolaris deploy/milvus-slim /home/nuvolaris/deploy/milvus-slim +ADD --chown=nuvolaris:nuvolaris deploy/registry /home/nuvolaris/deploy/registry ADD --chown=nuvolaris:nuvolaris quota.sh /home/nuvolaris/ #------------------------------------------------------------------------------ diff --git a/actions/devel/download/common/util.py b/actions/devel/download/common/util.py index f1e8f39b..11ba2e4e 100644 --- a/actions/devel/download/common/util.py +++ b/actions/devel/download/common/util.py @@ -16,7 +16,7 @@ # under the License. # -import mimetypes +import json def get_env_value(user_data, key): """ @@ -32,4 +32,15 @@ def get_env_value(user_data, key): if env['key'] == key: return env['value'] - return None \ No newline at end of file + return None + +def is_json(value): + if isinstance(value, (dict, list)): # Already a JSON-compatible structure + return True + if isinstance(value, str): # Try parsing if it's a string + try: + json.loads(value) + return True + except json.JSONDecodeError: + return False + return False \ No newline at end of file diff --git a/actions/devel/upload/common/util.py b/actions/devel/upload/common/util.py index f1e8f39b..11ba2e4e 100644 --- a/actions/devel/upload/common/util.py +++ b/actions/devel/upload/common/util.py @@ -16,7 +16,7 @@ # under the License. # -import mimetypes +import json def get_env_value(user_data, key): """ @@ -32,4 +32,15 @@ def get_env_value(user_data, key): if env['key'] == key: return env['value'] - return None \ No newline at end of file + return None + +def is_json(value): + if isinstance(value, (dict, list)): # Already a JSON-compatible structure + return True + if isinstance(value, str): # Try parsing if it's a string + try: + json.loads(value) + return True + except json.JSONDecodeError: + return False + return False \ No newline at end of file diff --git a/deploy/nuvolaris-permissions/whisk-crd.yaml b/deploy/nuvolaris-permissions/whisk-crd.yaml index 826cda53..dff969c2 100644 --- a/deploy/nuvolaris-permissions/whisk-crd.yaml +++ b/deploy/nuvolaris-permissions/whisk-crd.yaml @@ -146,7 +146,10 @@ spec: type: boolean milvus: description: deploys Milvus Operator to support deployment of MILVUS Vector DB - type: boolean + type: boolean + registry: + description: deploys a private registry to load on the fly generated action runtimes (false by default) + type: boolean required: - openwhisk - couchdb @@ -794,7 +797,45 @@ spec: type: string collection: description: nuvolaris MILVUS collection (default to nuvolaris) - type: string + type: string + registry: + description: used to configure the internal docker compliant registry + type: object + properties: + mode: + description: deployment mode can be internal/external (default to internal) + type: string + volume-size: + description: volume size in GB, default to 50GB + type: integer + auth: + description: used to configure the REGISTRY user to push and pull images from + type: object + properties: + username: + type: string + password: + type: string + required: + - username + - password + hostname: + description: used to configure the repo hostname (if set to auto and mode=internal it will be img.) + type: string + ingress: + description: configuration option for global REGISTRY ingresses exposure, will be taken into account only if deployment=interna + type: object + properties: + enabled: + description: boolean flag to activate a REGISTRY ingress. Default to false + type: boolean + required: + - enabled + required: + - mode + - volume-size + - auth + - hostname status: x-kubernetes-preserve-unknown-fields: true # type: object @@ -902,4 +943,9 @@ spec: type: string priority: 0 jsonPath: .status.whisk_create.milvus - description: Milvus \ No newline at end of file + description: Milvus + - name: Registry + type: string + priority: 0 + jsonPath: .status.whisk_create.registry + description: Registry \ No newline at end of file diff --git a/deploy/openwhisk-invoker/invoker-conf.yaml b/deploy/openwhisk-invoker/invoker-conf.yaml new file mode 100644 index 00000000..6b59561e --- /dev/null +++ b/deploy/openwhisk-invoker/invoker-conf.yaml @@ -0,0 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: openwhisk-pod-templates + namespace: nuvolaris +data: + ow_pod_templates.yaml: | + apiVersion: v1 + kind: Pod + spec: + imagePullSecrets: + - name: registry-pull-secret + diff --git a/deploy/openwhisk-standalone/standalone-conf.yaml b/deploy/openwhisk-standalone/standalone-conf.yaml new file mode 100644 index 00000000..6b59561e --- /dev/null +++ b/deploy/openwhisk-standalone/standalone-conf.yaml @@ -0,0 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: openwhisk-pod-templates + namespace: nuvolaris +data: + ow_pod_templates.yaml: | + apiVersion: v1 + kind: Pod + spec: + imagePullSecrets: + - name: registry-pull-secret + diff --git a/deploy/registry/00-registry-secret-docker.yaml b/deploy/registry/00-registry-secret-docker.yaml new file mode 100644 index 00000000..4a01bcb5 --- /dev/null +++ b/deploy/registry/00-registry-secret-docker.yaml @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +--- +apiVersion: v1 +kind: Secret +metadata: + name: registry-pull-secret + namespace: nuvolaris +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: eyJhdXRocyI6IHsibnV2b2xhcmlzLXJlZ2lzdHJ5LXN2YyI6IHsidXNlcm5hbWUiOiAib3BlbnNlcnZlcmxlc3MiLCAicGFzc3dvcmQiOiAiNHB3ZHJlZ2lzdHJ5IiwgImF1dGgiOiAiYjNCbGJuTmxjblpsY214bGMzTTZOSEIzWkhKbFoybHpkSEo1In19fQ== \ No newline at end of file diff --git a/deploy/registry/00-registry-secret.yaml b/deploy/registry/00-registry-secret.yaml new file mode 100644 index 00000000..1219339a --- /dev/null +++ b/deploy/registry/00-registry-secret.yaml @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +--- +apiVersion: v1 +data: + htpasswd: bXl1c2VyOiQyeSQwNSRVWm9Tb044Q1JWc0VSaUZieVJvWWV1eTU1NGxmOTZtYWZBR1BrODJYeFppbE55bHVHa0dNbQoK +kind: Secret +metadata: + name: registry-auth-secret + namespace: nuvolaris +type: Opaque diff --git a/deploy/registry/01-registry-pvc.yaml b/deploy/registry/01-registry-pvc.yaml new file mode 100644 index 00000000..acd20788 --- /dev/null +++ b/deploy/registry/01-registry-pvc.yaml @@ -0,0 +1,32 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: registry-pvc + namespace: nuvolaris + labels: + app: registry-pvc +spec: + accessModes: + - ReadWriteOnce + storageClassName: standard + resources: + requests: + storage: 10Gi \ No newline at end of file diff --git a/deploy/registry/02-registry-sts.yaml b/deploy/registry/02-registry-sts.yaml new file mode 100644 index 00000000..83f579bd --- /dev/null +++ b/deploy/registry/02-registry-sts.yaml @@ -0,0 +1,66 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: registry + namespace: nuvolaris + labels: + name: registry + app: registry +spec: + replicas: 1 + serviceName: nuvolaris-registry-svc + selector: + matchLabels: + app: registry + name: registry + template: + metadata: + labels: + app: registry + name: registry + spec: + containers: + - name: registry + image: registry:3.0.0 + ports: + - containerPort: 5000 + env: + - name: REGISTRY_AUTH + value: "htpasswd" + - name: REGISTRY_AUTH_HTPASSWD_REALM + value: "Registry Realm" + - name: REGISTRY_AUTH_HTPASSWD_PATH + value: "/auth/htpasswd" + - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY + value: "/var/lib/registry" + volumeMounts: + - name: registry-pvc + mountPath: /var/lib/registry + - name: auth-volume + mountPath: /auth + readOnly: true + volumes: + - name: registry-pvc + persistentVolumeClaim: + claimName: registry-pvc + - name: auth-volume + secret: + secretName: registry-auth-secret \ No newline at end of file diff --git a/deploy/registry/03-registry-svc.yaml b/deploy/registry/03-registry-svc.yaml new file mode 100644 index 00000000..d0485ed2 --- /dev/null +++ b/deploy/registry/03-registry-svc.yaml @@ -0,0 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +--- +apiVersion: v1 +kind: Service +metadata: + name: nuvolaris-registry-svc + namespace: nuvolaris +spec: + selector: + app: registry + ports: + - protocol: TCP + port: 5000 + targetPort: 5000 + type: ClusterIP \ No newline at end of file diff --git a/nuvolaris/apihost_util.py b/nuvolaris/apihost_util.py index 8c44603a..fa24bbef 100644 --- a/nuvolaris/apihost_util.py +++ b/nuvolaris/apihost_util.py @@ -110,9 +110,11 @@ def calculate_apihost(runtime_str,apiHost=None): elif "ip" in apiHost[0]: url = url._replace(netloc = ensure_host(apiHost[0]['ip'])) - # in auto mode we should use the calculated ip address + # in auto mode we should use the calculated ip address and defaults to miniops.me if cfg.exists("nuvolaris.apihost") and not 'auto' == cfg.get('nuvolaris.apihost'): url = url._replace(netloc = ensure_host(cfg.get("nuvolaris.apihost"))) + else: + url = url._replace(netloc = "miniops.me" ) if cfg.exists("nuvolaris.apiport"): url = url._replace(netloc = f"{url.hostname}:{cfg.get('nuvolaris.apiport')}") diff --git a/nuvolaris/enterprise_util.py b/nuvolaris/enterprise_util.py index 44df7425..fd0d0bb8 100644 --- a/nuvolaris/enterprise_util.py +++ b/nuvolaris/enterprise_util.py @@ -89,7 +89,8 @@ def getEnterpriseControllerConfigData(): "container_cpu_lim": cfg.get('configs.controller.resources.cpu-lim') or "2", "container_mem_req": cfg.get('configs.controller.resources.mem-req') or "1G", "container_mem_lim": cfg.get('configs.controller.resources.mem-lim') or "2G", - "container_manage_resources": cfg.exists('configs.controller.resources.cpu-req') + "container_manage_resources": cfg.exists('configs.controller.resources.cpu-req'), + "usePrivateRegistry":cfg.get('components.registry') or False, } util.get_controller_image_data(data) @@ -148,6 +149,7 @@ def getEnterpriseInvokerConfigData(): "kubernetes_user_pod_affinity_enabled":cfg.get('configs.invoker.kubernetes.user_pod_affinity_enabled') and "true" or "false", "kubernetes_user_pod_affinity_key":cfg.get('configs.invoker.kubernetes.user_pod_affinity_key') or "nuvolaris-role", "kubernetes_user_pod_affinity_value":cfg.get('configs.invoker.kubernetes.user_pod_affinity_value') or "invoker", + "usePrivateRegistry":cfg.get('components.registry') or False, } util.get_controller_image_data(data) diff --git a/nuvolaris/etcd.py b/nuvolaris/etcd.py index 10fedc88..f24e2840 100644 --- a/nuvolaris/etcd.py +++ b/nuvolaris/etcd.py @@ -43,7 +43,7 @@ def create(owner=None): spec_templates.append("etcd-policy.yaml") kust = kus.patchTemplates("etcd",tplp , data) - kust += kus.patchGenericEntry("Secret","nuvolaris-etcd-secret","/data/rootPassword",util.b64_encode(data['root_password'])) + kust += kus.patchGenericEntry("Secret","nuvolaris-etcd-secret","/data/rootPassword",util.b64_encode(data['root_password'])) spec = kus.kustom_list("etcd", kust, templates=spec_templates, data=data) if owner: diff --git a/nuvolaris/kopf_util.py b/nuvolaris/kopf_util.py index 1740f6a6..72b619ef 100644 --- a/nuvolaris/kopf_util.py +++ b/nuvolaris/kopf_util.py @@ -73,6 +73,14 @@ def check_minio_ingresses(response: dict, item: dict): if(item['new']): response["minio-ingresses"]="update" +def check_registry_ingresses(response: dict, item: dict): + """ + Forces an update of registry-ingress if needed + """ + if item['path']=='spec.registry.ingress.enabled': + if(item['new']): + response["registry-ingresses"]="update" + def evaluate_differences(response: dict, differences: list): """ Iterate over the difference list to find which components the @@ -91,6 +99,7 @@ def evaluate_differences(response: dict, differences: list): check_component(response, d,"spec.components.quota","quota") check_component(response, d,"spec.components.etcd","etcd") check_component(response, d,"spec.components.milvus","milvus") + check_component(response, d,"spec.components.registry","registry") openwhisk(response, d) endpoint(response, d) check_minio_ingresses(response, d) diff --git a/nuvolaris/main.py b/nuvolaris/main.py index edfff456..bae0217c 100644 --- a/nuvolaris/main.py +++ b/nuvolaris/main.py @@ -41,6 +41,7 @@ import nuvolaris.quota_checker_job as quota import nuvolaris.etcd as etcd import nuvolaris.milvus_standalone as milvus +import nuvolaris.registry_deploy as registry @kopf.on.startup() def configure(settings: kopf.OperatorSettings, **_): @@ -119,6 +120,17 @@ def whisk_create(spec, name, **kwargs): else: state['redis'] = "off" + if cfg.get('components.registry'): + try: + msg = registry.create(owner) + state['registry'] = "on" + logging.info(msg) + except: + logging.exception("cannot create registry") + state['registry']= "error" + else: + state['registry'] = "off" + if cfg.get('components.tls') and not runtime in ["kind","openshift"]: try: msg = issuer.create(owner) @@ -262,7 +274,7 @@ def whisk_create(spec, name, **kwargs): logging.exception("cannot create milvus") state['milvus']= "error" else: - state['milvus'] = "off" + state['milvus'] = "off" whisk_post_create(name,state) state['controller']= "Ready" @@ -346,7 +358,11 @@ def whisk_delete(spec, **kwargs): if cfg.get("components.milvus"): msg = milvus.delete() - logging.info(msg) + logging.info(msg) + + if cfg.get("components.registry"): + msg = registry.delete() + logging.info(msg) # tested by integration test diff --git a/nuvolaris/minio_ingress.py b/nuvolaris/minio_ingress.py index 7953ee43..14d6d397 100644 --- a/nuvolaris/minio_ingress.py +++ b/nuvolaris/minio_ingress.py @@ -89,7 +89,7 @@ def deploy_minio_upload_route(apihost,namespace, type): upload.with_context_path("/api/upload") upload.with_rewrite_target("/") - logging.info(f"*** configuring route for upload") + logging.info("*** configuring route for upload") path_to_template_yaml = upload.render_template(namespace) res = kube.kubectl("apply", "-f",path_to_template_yaml) os.remove(path_to_template_yaml) diff --git a/nuvolaris/patcher.py b/nuvolaris/patcher.py index 37c0e7ce..280b1103 100644 --- a/nuvolaris/patcher.py +++ b/nuvolaris/patcher.py @@ -35,6 +35,7 @@ import nuvolaris.quota_checker_job as quota import nuvolaris.etcd as etcd import nuvolaris.milvus_standalone as milvus +import nuvolaris.registry_deploy as registry def patch_preloader(owner: None): try: @@ -157,7 +158,11 @@ def patch(diff, status, owner=None, name=None): if "milvus" in what_to_do: milvus.patch(status,what_to_do['milvus'], owner) - components_updated = True + components_updated = True + + if "registry" in what_to_do: + registry.patch(status,what_to_do['milvus'], owner) + components_updated = True # handle update action on openwhisk if "openwhisk" in what_to_do and what_to_do['openwhisk'] == "update": @@ -173,7 +178,5 @@ def patch(diff, status, owner=None, name=None): minio.patch_ingresses(status,what_to_do['minio-ingresses'], owner) if components_updated: - operator_util.whisk_post_create(name) - - + operator_util.whisk_post_create(name) diff --git a/nuvolaris/registry_deploy.py b/nuvolaris/registry_deploy.py new file mode 100644 index 00000000..c61bccb3 --- /dev/null +++ b/nuvolaris/registry_deploy.py @@ -0,0 +1,283 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +import nuvolaris.kustomize as kus +import nuvolaris.kube as kube +import nuvolaris.config as cfg +import nuvolaris.util as util +import logging +import kopf +import os +import nuvolaris.apihost_util as apihost_util +import nuvolaris.openwhisk as openwhisk +import nuvolaris.endpoint as endpoint +import nuvolaris.operator_util as operator_util + +from nuvolaris.secret_htpasswd_data import SecretHtpasswordData +from nuvolaris.secret_imagepull_data import ImagePullSecretData +from nuvolaris.ingress_data import IngressData +from nuvolaris.route_data import RouteData + + +def create_external_registry(data, owner=None): + logging.info("setting up an external registry") + + #set the registry secret + registrySecret = SecretHtpasswordData(data['registryUsername'],data['registryPassword']) + registrySecret.with_secret_name("registry-auth-secret") + path_to_template_yaml = registrySecret.render_template("nuvolaris") + res = kube.kubectl("apply", "-f",path_to_template_yaml) + os.remove(path_to_template_yaml) + + #set the registry pull secret + registryPullSecret = ImagePullSecretData(data['registryUsername'],data['registryPassword'],data['repoHostname']) + registryPullSecret.with_secret_name("registry-pull-secret") + path_to_template_yaml = registryPullSecret.render_template("nuvolaris") + + res += kube.kubectl("apply", "-f",path_to_template_yaml) + os.remove(path_to_template_yaml) + _annotate_registry_metadata(data) + + return res + +def create_internal_registry(data, owner=None): + logging.info("setting up an internal registry") + tplp = ["pvc-attach.yaml"] + + if(data['affinity'] or data['tolerations']): + tplp.append("affinity-tolerance-sts-core-attach.yaml") + + kust = kus.patchTemplates("registry",tplp , data) + + #patch the registry secret + registrySecret = SecretHtpasswordData(data['registryUsername'],data['registryPassword']) + registrySecret.with_secret_name("registry-auth-secret") + kust += registrySecret.generateHtPasswordPatch() + + #path the registry pull secret + registryPullSecret = ImagePullSecretData(data['registryUsername'],data['registryPassword'],data['repoHostname']) + registryPullSecret.with_secret_name("registry-pull-secret") + kust += registryPullSecret.generatePullSecretPatch() + + spec = kus.kustom_list("registry", kust, templates=[], data=data) + + if owner: + kopf.append_owner_reference(spec['items'], owner) + else: + cfg.put("state.registry.spec", spec) + res = kube.apply(spec) + + wait_for_registry_ready() + _annotate_registry_metadata(data) + + if data['ingressEnabled']: + create_registry_ingress(data) + + logging.info(f"created internal registry: {res}") + return res + +def create(owner=None): + data = util.get_registry_config_data() + assign_registry_hostname(data) + + if data['mode'] == 'internal': + return create_internal_registry(data, owner) + else: + return create_external_registry(data, owner) + +def _annotate_registry_metadata(data): + """ + annotate nuvolaris configmap with entries for registry connectivity REGISTRY_ENDPOINT, REGISTRY_USERNAME, RESIGTRY_PASSWORD + """ + try: + openwhisk.annotate(f"registry_host={data['repoHostname']}") + openwhisk.annotate(f"registry_internal_host={data['repoSvcHostname']}") + openwhisk.annotate(f"registry_username={data['registryUsername']}") + openwhisk.annotate(f"registry_password={data['registryPassword']}") + + if data['repoUrl']: + openwhisk.annotate(f"registry_url={data['repoUrl']}") + + return None + except Exception as e: + logging.error(f"failed to annotate registry_host for nuvolaris: {e}") + return None + +# +# Determine the registry hostname to be set, when an internal registry must be set +# +def assign_registry_hostname(data): + if data['repoHostname'] not in ["auto"]: + return + + if not data['ingressEnabled']: + data['repoHostname'] = data['repoSvcHostname'] + return + + runtime = cfg.get('nuvolaris.kube') + repoUrl = apihost_util.append_prefix_to_url(apihost_util.get_apihost(runtime), "img") + data['repoHostname'] = apihost_util.extract_hostname(repoUrl) + data['repoUrl'] = repoUrl + + logging.info(f"assigned registry hostname {data['repoHostname']}") + + +def wait_for_registry_ready(): + # dynamically detect registry pod and wait for readiness + util.wait_for_pod_ready("{.items[?(@.metadata.labels.name == 'registry')].metadata.name}") + +def create_registry_ingress(data, owner=None): + """ + Creates all the REGISTRY related ingresses according to provide configuration + """ + if cfg.get('nuvolaris.kube') == 'openshift': + return deploy_registry_route(data) + else: + return deploy_registry_ingress(data) + +def deploy_registry_route(data, namespace="nuvolaris"): + """ + Deploys a generic REGISTRY route ingress + param: data + param: namespace + """ + route = RouteData(data['repoUrl']) + route.with_route_name(endpoint.api_route_name(namespace,"registry")) + route.with_service_name("nuvolaris-registry-svc") + route.with_service_kind("Service") + route.with_service_port("5000") + route.with_context_path("/") + + logging.info("*** configuring registry route for service nuvolaris-registry-svc:5000") + path_to_template_yaml = route.render_template(namespace) + res = kube.kubectl("apply", "-f",path_to_template_yaml) + os.remove(path_to_template_yaml) + return res + +def deploy_registry_ingress(data, namespace="nuvolaris"): + """ + Deploys a generic MINIO nginx/traefik ingress + param: apihost + param: namespace + param: type (s3, console) + param: service_name (normally it is minio) + param: port (9000 or 9090) + paramL context_path (/) + """ + ingress = IngressData(data['repoUrl']) + ingress.with_ingress_name(endpoint.api_ingress_name(namespace, "registry")) + ingress.with_secret_name(endpoint.ingress_secret_name(namespace, "registry")) + ingress.with_context_path("/") + ingress.with_service_name("nuvolaris-registry-svc") + ingress.with_service_port("5000") + + if ingress.requires_traefik_middleware(): + logging.info("*** configuring traefik middleware for registry ingress") + path_to_template_yaml = ingress.render_traefik_middleware_template(namespace) + res = kube.kubectl("apply", "-f",path_to_template_yaml) + os.remove(path_to_template_yaml) + + logging.info("*** configuring static ingress for registry") + path_to_template_yaml = ingress.render_template(namespace) + res = kube.kubectl("apply", "-f",path_to_template_yaml) + os.remove(path_to_template_yaml) + + return res + +def delete_registry_ingress(owner=None, namespace="nuvolaris"): + """ + undeploys ingresses for registry apihost + """ + logging.info("*** removing ingresses for REGISTRY") + runtime = cfg.get('nuvolaris.kube') + ingress_class = util.get_ingress_class(runtime) + + try: + res = "" + if(runtime=='openshift'): + res = kube.kubectl("delete", "route",endpoint.api_route_name(namespace,"registry")) + return res + + res += kube.kubectl("delete", "ingress",endpoint.api_ingress_name(namespace,"registry")) + + if(ingress_class == 'traefik'): + res = kube.kubectl("delete", "middleware.traefik.containo.us",endpoint.api_middleware_ingress_name(namespace,"registry")) + + return res + except Exception as e: + logging.warning(e) + return None + +def delete_by_owner(): + spec = kus.build("registry") + res = kube.delete(spec) + logging.info(f"delete registry: {res}") + return res + +def delete_by_spec(): + spec = cfg.get("state.registry.spec") + res = False + if spec: + res = kube.delete(spec) + logging.info(f"delete registry: {res}") + return res + +def delete(owner=None): + delete_registry_ingress(owner) + + if owner: + return delete_by_owner() + else: + return delete_by_spec() + +def patch(status, action, owner=None): + """ + Called by the operator patcher to create/delete registry component + """ + try: + logging.info(f"*** handling request to {action} registry") + if action == 'create': + msg = create(owner) + operator_util.patch_operator_status(status, 'registry', 'on') + else: + msg = delete(owner) + operator_util.patch_operator_status(status, 'registry', 'off') + + logging.info(msg) + logging.info(f"*** handled request to {action} registry") + except Exception as e: + logging.error('*** failed to update milvus: %s' % e) + operator_util.patch_operator_status(status, 'registry', 'error') + +def patch_ingresses(status, action, owner=None): + """ + Called by the operator patcher to create/delete registry component + """ + try: + logging.info(f"*** handling request to {action} registry ingresses") + data = util.get_registry_config_data() + assign_registry_hostname(data) + + if action == 'update': + msg = create_registry_ingress(data, owner) + operator_util.patch_operator_status(status,'registry-ingresses','on') + + logging.info(msg) + logging.info(f"*** hanlded request to {action} registry ingresses") + except Exception as e: + logging.error('*** failed to update minio ingresses: %s' % e) + operator_util.patch_operator_status(status,'registry-ingresses','error') diff --git a/nuvolaris/secret_htpasswd_data.py b/nuvolaris/secret_htpasswd_data.py new file mode 100644 index 00000000..7a636d4b --- /dev/null +++ b/nuvolaris/secret_htpasswd_data.py @@ -0,0 +1,72 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +import json +import logging +import os +import nuvolaris.kustomize as kus +import nuvolaris.template as ntp +import bcrypt +import base64 + +class SecretHtpasswordData: + + def __init__(self, username, password): + self._data = { + "htpasswd":self.generate_htpasswd(username, password), + 'namespace':'nuvolaris' + } + + def generate_htpasswd_lines(self, username: str, password: str): + htpasswd_lines = [] + hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt()) + htpasswd_lines.append(f"{username}:{hashed.decode()}") + return "\n".join(htpasswd_lines) + + # base64 encode the content of htpasswd file + def generate_htpasswd(self, username: str, password: str): + htpasswd_lines = self.generate_htpasswd_lines(username, password) + return base64.b64encode(htpasswd_lines.encode()).decode() + + def dump(self): + logging.debug(json.dumps(self._data)) + + def with_secret_name(self,value: str): + self._data['secret_name']=value + + def with_namespace(self,value: str): + self._data['namespace']=value + + def build_secret_spec(self, where: str, out_template=None, tpl = "generic-secret-htpassword-tpl.yaml"): + logging.info(f"*** Building htpassword secret template with name {self._data['secret_name']} via template {tpl}") + return kus.processTemplate(where, tpl, self._data, out_template) + + 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 + """ + 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) + return os.path.abspath(file) + + def generateHtPasswordPatch(self): + """ + generate a patch entry for this secret + """ + return kus.patchGenericEntry("Secret", self._data['secret_name'],"/data/htpasswd", self._data['htpasswd']) + \ No newline at end of file diff --git a/nuvolaris/secret_imagepull_data.py b/nuvolaris/secret_imagepull_data.py new file mode 100644 index 00000000..76fae80c --- /dev/null +++ b/nuvolaris/secret_imagepull_data.py @@ -0,0 +1,78 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +import json +import logging +import os +import nuvolaris.kustomize as kus +import nuvolaris.template as ntp +import base64 + +class ImagePullSecretData: + + def __init__(self, username, password, registry): + self._data = { + "dockerauths":self.generate_docker_auths(username, password,registry), + 'namespace':'nuvolaris' + } + + def generate_docker_config(self, username: str, password: str, registry: str): + # Construct Docker config JSON + docker_config = { + "auths": { + registry: { + "username": username, + "password": password, + "auth": base64.b64encode(f"{username}:{password}".encode()).decode() + } + } + } + return json.dumps(docker_config) + + # base64 encode the content of docker_config json file + def generate_docker_auths(self, username: str, password: str, registry: str): + docker_config_json = self.generate_docker_config(username, password, registry) + return base64.b64encode(docker_config_json.encode()).decode() + + def dump(self): + logging.debug(json.dumps(self._data)) + + def with_secret_name(self,value: str): + self._data['secret_name']=value + + def with_namespace(self,value: str): + self._data['namespace']=value + + def build_secret_spec(self, where: str, out_template=None, tpl = "generic-secret-docker-tpl.yaml"): + logging.info(f"*** Building ImagePull secret template with name {self._data['secret_name']} via template {tpl}") + return kus.processTemplate(where, tpl, self._data, out_template) + + 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 + """ + logging.info(f"*** Rendering ImagePull secret template with name {self._data['secret_name']} via template {tpl}") + out = f"/tmp/__{namespace}_{tpl}" + file = ntp.spool_template(tpl, out, self._data) + return os.path.abspath(file) + + def generatePullSecretPatch(self): + """ + generate a patch entry for this secret + """ + return kus.patchGenericEntry("Secret", self._data['secret_name'],"/data/.dockerconfigjson", self._data['dockerauths']) + \ No newline at end of file diff --git a/nuvolaris/templates/enterprise-sts.yaml b/nuvolaris/templates/enterprise-sts.yaml index 19326876..db8320a7 100644 --- a/nuvolaris/templates/enterprise-sts.yaml +++ b/nuvolaris/templates/enterprise-sts.yaml @@ -234,4 +234,4 @@ spec: #- name: "CONFIG_spi_LogStoreProvider" # value: "org.apache.openwhisk.core.containerpool.logging.DockerToActivationLogStoreProvider" #- name: "CONFIG_spi_ArtifactStoreProvider" - # value: "org.apache.openwhisk.core.database.CouchDbStoreProvider" + # value: "org.apache.openwhisk.core.database.CouchDbStoreProvider" diff --git a/nuvolaris/templates/generic-secret-docker-tpl.yaml b/nuvolaris/templates/generic-secret-docker-tpl.yaml new file mode 100644 index 00000000..c55b0d38 --- /dev/null +++ b/nuvolaris/templates/generic-secret-docker-tpl.yaml @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +--- +apiVersion: networking.k8s.io/v1 +kind: Secret +metadata: + name: {{secret_name}} + namespace: {{namespace}} +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: {{dockerauths}} diff --git a/nuvolaris/templates/generic-secret-htpassword-tpl.yaml b/nuvolaris/templates/generic-secret-htpassword-tpl.yaml new file mode 100644 index 00000000..9c9a87b8 --- /dev/null +++ b/nuvolaris/templates/generic-secret-htpassword-tpl.yaml @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +--- +apiVersion: networking.k8s.io/v1 +kind: Secret +metadata: + name: {{secret_name}} + namespace: {{namespace}} +data: + htpasswd: {{htpasswd}} +type: Opaque diff --git a/nuvolaris/templates/invoker-sts.yaml b/nuvolaris/templates/invoker-sts.yaml index 6d49b798..1f27bcf9 100644 --- a/nuvolaris/templates/invoker-sts.yaml +++ b/nuvolaris/templates/invoker-sts.yaml @@ -59,7 +59,7 @@ spec: limits: memory: "{{container_mem_lim}}" cpu : "{{container_cpu_lim}}" - {% endif %} + {% endif %} env: - name: "CONFIG_whisk_info_date" @@ -303,6 +303,23 @@ spec: - name: "CONFIG_whisk_kubernetes_userPodNodeAffinity_value" value: "{{kubernetes_user_pod_affinity_value}}" + {% if usePrivateRegistry %} + - name: "CONFIG_whisk_kubernetes_podTemplate" + value: "file:/templates/ow_pod_templates.yaml" + {% endif %} + ports: - name: invoker containerPort: {{invoker_port}} + volumeMounts: + - mountPath: /templates/ow_pod_templates.yaml + name: templates + subPath: ow_pod_templates.yaml + volumes: + - name: templates + configMap: + name: openwhisk-pod-templates + defaultMode: 511 + items: + - key: ow_pod_templates.yaml + path: ow_pod_templates.yaml diff --git a/nuvolaris/templates/standalone-sts.yaml b/nuvolaris/templates/standalone-sts.yaml index f8fd6133..2ee9da87 100644 --- a/nuvolaris/templates/standalone-sts.yaml +++ b/nuvolaris/templates/standalone-sts.yaml @@ -82,7 +82,7 @@ spec: limits: memory: "{{container_mem_lim}}" cpu : "{{container_cpu_lim}}" - {% endif %} + {% endif %} env: - name: "PORT" @@ -253,6 +253,22 @@ spec: value: "3233" - name: "WHISK_API_HOST_NAME" - value: "controller.nuvolaris.svc.cluster.local" - + value: "controller.nuvolaris.svc.cluster.local" + + {% if usePrivateRegistry %} + - name: "CONFIG_whisk_kubernetes_podTemplate" + value: "file:/templates/ow_pod_templates.yaml" + {% endif %} + volumeMounts: + - mountPath: /templates/ow_pod_templates.yaml + name: templates + subPath: ow_pod_templates.yaml + volumes: + - name: templates + configMap: + name: openwhisk-pod-templates + defaultMode: 511 + items: + - key: ow_pod_templates.yaml + path: ow_pod_templates.yaml diff --git a/nuvolaris/util.py b/nuvolaris/util.py index d14fb1b9..f2b6eb69 100644 --- a/nuvolaris/util.py +++ b/nuvolaris/util.py @@ -31,7 +31,7 @@ import nuvolaris.apihost_util as apihost_util import nuvolaris.config as cfg import nuvolaris.kube as kube - +import nuvolaris.template as template # Implements truncated exponential backoff from # https://cloud.google.com/storage/docs/retry-strategy#exponential-backoff @@ -200,16 +200,6 @@ def wait_for_pod_ready(pod_name_jsonpath, timeout="600s", namespace="nuvolaris") logging.error(e) -def status_matches(code: int, allowed: List[Union[int, str]]) -> bool: - """Check if the status code matches any allowed pattern.""" - for pattern in allowed: - if isinstance(pattern, int) and code == pattern: - return True - if isinstance(pattern, str) and len(pattern) == 3 and pattern.endswith("XX"): - if int(pattern[0]) == code // 100: - return True - return False - def status_matches(code: int, allowed: List[Union[int, str]]) -> bool: """Check if the status code matches any allowed pattern.""" for pattern in allowed: @@ -344,7 +334,8 @@ def get_standalone_config_data(): "container_cpu_lim": cfg.get('configs.controller.resources.cpu-lim') or "1", "container_mem_req": cfg.get('configs.controller.resources.mem-req') or "1G", "container_mem_lim": cfg.get('configs.controller.resources.mem-lim') or "2G", - "container_manage_resources": cfg.exists('configs.controller.resources.cpu-req') + "container_manage_resources": cfg.exists('configs.controller.resources.cpu-req'), + "usePrivateRegistry":cfg.get('components.registry') or False, } get_controller_image_data(data) @@ -594,6 +585,11 @@ def postgres_backup_affinity_tolerations_data(data): common_affinity_tolerations_data(data) data["pod_anti_affinity_name"] = "nuvolaris-postgres-backup" +# populate specific affinity data for registry +def registry_affinity_tolerations_data(data): + common_affinity_tolerations_data(data) + data["pod_anti_affinity_name"] = "registry" + # wait for a pod name using a label selector and eventually an optional jsonpath @nuv_retry() def get_pod_name_by_selector(selector, jsonpath, namespace="nuvolaris"): @@ -823,3 +819,27 @@ def get_milvus_config_data(): return data +# return registry configuration parameters with default values if not configured +def get_registry_config_data(): + + data = { + "applypodsecurity":get_enable_pod_security(), + "name": "registry", + "container": "registry", + "dir":"/var/lib/registry", + "pvcName":"registry-pvc", + "size": cfg.get("registry.volume-size", "REGISTRY_VOLUME_SIZE", 20), + "storageClass": cfg.get("nuvolaris.storageclass"), + "repoHostname": cfg.get('registry.hostname') or "auto", + "ingressEnabled": cfg.get('registry.ingress.enabled') or False, + "registryUsername": cfg.get('registry.auth.username') or "openserverless", + "registryPassword": cfg.get('registry.auth.password') or "4pwdregistry", + "mode": cfg.get('registry.mode') or "internal" + } + + # always add the internal SvcHostname + data['repoSvcHostname'] = "nuvolaris-registry-svc:5000" + registry_affinity_tolerations_data(data) + return data + + diff --git a/tests/kind/registry_test.ipy b/tests/kind/registry_test.ipy new file mode 100644 index 00000000..5e74e2a2 --- /dev/null +++ b/tests/kind/registry_test.ipy @@ -0,0 +1,45 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +import os + +import nuvolaris.config as cfg +import nuvolaris.kube as kube +import nuvolaris.registry_deploy as registry_deploy +import nuvolaris.testutil as tu +import nuvolaris.util as util +import logging + +logging.basicConfig(level=logging.DEBUG) + +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())) +assert(cfg.detect_storage()["nuvolaris.storageclass"]) + +# for this test minioClient should see this env variable +assert(registry_deploy.create()) + +pod_name = util.get_pod_name("{.items[?(@.metadata.labels.app == 'registry')].metadata.name}") +assert(pod_name) + +cm_registry_url = kube.kubectl("get", f"cm/config", jsonpath="{.metadata.annotations.registry_host}") +assert(cm_registry_url) + +assert(registry_deploy.delete()) \ No newline at end of file diff --git a/tests/kind/whisk-slim.yaml b/tests/kind/whisk-slim.yaml index d214be19..5c41565f 100644 --- a/tests/kind/whisk-slim.yaml +++ b/tests/kind/whisk-slim.yaml @@ -35,7 +35,7 @@ spec: # start mongodb mongodb: false # start redis - redis: false + redis: true # start cron based action parser cron: false # tls enabled or not @@ -49,7 +49,9 @@ spec: # etcd enabled or not etcd: true # milvus enabled or not - milvus: true + milvus: true + # registry enabled or not + registry: true openwhisk: namespaces: whisk-system: 789c46b1-71f6-4ed5-8c54-816aa4f8c502:abczO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP @@ -156,4 +158,13 @@ spec: password: root: An0therPa55 etcd: 97Vk2{qe8o>S - s3: 8_d$8zCrl7£m + s3: 8_d$8zCrl7£m + registry: + mode: internal + volume-size: 20 + auth: + username: nuvolaris + password: 4pwdregistry + hostname: auto + ingress: + enabled: false \ No newline at end of file diff --git a/tests/kind/whisk.yaml b/tests/kind/whisk.yaml index 24bd49df..b4b04fdb 100644 --- a/tests/kind/whisk.yaml +++ b/tests/kind/whisk.yaml @@ -47,7 +47,9 @@ spec: # etcd enabled or not etcd: true # milvus enabled or not - milvus: true + milvus: true + # milvus enabled or not + registry: true openwhisk: namespaces: whisk-system: 789c46b1-71f6-4ed5-8c54-816aa4f8c502:abczO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP @@ -176,3 +178,12 @@ spec: root: An0therPa55 etcd: 97Vk2{qe8o>S s3: 8_d$8zCrl7£m + registry: + mode: internal + volume-size: 20 + auth: + username: nuvolaris + password: 4pwdregistry + hostname: auto + ingress: + enabled: true diff --git a/tests/whisk.yaml b/tests/whisk.yaml index df19db46..5f874b0d 100644 --- a/tests/whisk.yaml +++ b/tests/whisk.yaml @@ -49,7 +49,9 @@ spec: # etcd enabled or not etcd: true # milvus enabled or not - milvus: true + milvus: true + # registry enabled or not + registry: true openwhisk: namespaces: whisk-system: 789c46b1-71f6-4ed5-8c54-816aa4f8c502:abczO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP @@ -146,3 +148,12 @@ spec: replicas: 1 root: password: 0therPa55 + registry: + mode: internal + volume-size: 20 + auth: + username: nuvolaris + password: 4pwdregistry + hostname: auto + ingress: + enabled: false