From 878bd69b3ac75f0b9455d4bac85063054189b3ad Mon Sep 17 00:00:00 2001 From: Mohammad Abudayyeh <47318409+moabu@users.noreply.github.com> Date: Wed, 19 Jan 2022 03:59:57 -0500 Subject: [PATCH] chore: add official helm chart (#621) * feat: move main installation https://github.com/GluuFederation/cloud-native-edition/tree/master * ci: add iromli as a CODEOWNER * docs: fix paths --- .github/CODEOWNERS | 2 +- .github/workflows/microk8s_couchbase.yml | 4 +- .github/workflows/microk8s_mysql.yml | 4 +- .github/workflows/microk8s_opendj.yml | 4 +- .github/workflows/release.yaml | 6 +- README.md | 8 +- _config.yml | 2 +- automation/startdemo.sh | 32 - automation/startopenabankingdemo.sh | 97 + charts/artifacthub-repo.yml | 1 - charts/index.yaml | 596 ---- charts/jans-1.0.0-a4.tgz | Bin 34872 -> 0 bytes charts/jans-1.0.0-b10.tgz | Bin 192583 -> 0 bytes charts/jans-1.0.0-b11.tgz | Bin 63966 -> 0 bytes charts/jans-1.0.0-b6.tgz | Bin 35289 -> 0 bytes charts/jans-1.0.0-b7.tgz | Bin 63547 -> 0 bytes charts/jans-1.0.0-b8.tgz | Bin 62582 -> 0 bytes charts/jans-1.0.0-b9.tgz | Bin 62067 -> 0 bytes charts/jans/Chart.yaml | 94 - .../auth-server-key-rotation/Chart.yaml | 20 - .../templates/service.yaml | 18 - .../templates/user-custom-secret-envs.yaml | 13 - charts/jans/charts/auth-server/Chart.yaml | 22 - .../auth-server-destination-rules.yaml | 14 - .../charts/auth-server/templates/service.yml | 19 - .../templates/user-custom-secret-envs.yaml | 13 - .../charts/client-api/templates/service.yaml | 18 - .../templates/user-custom-secret-envs.yaml | 13 - .../jans/charts/cn-istio-ingress/Chart.yaml | 21 - charts/jans/charts/cn-istio-ingress/README.md | 25 - .../charts/config-api/templates/service.yaml | 18 - charts/jans/charts/config/Chart.yaml | 22 - .../jans/charts/config/templates/secrets.yaml | 122 - .../config/templates/user-custom-envs.yaml | 36 - .../jans/charts/fido2/templates/service.yml | 19 - .../templates/user-custom-secret-envs.yaml | 13 - .../charts/opendj/templates/configmaps.yaml | 12 - .../jans/charts/opendj/templates/secrets.yaml | 11 - .../templates/user-custom-secret-envs.yaml | 13 - charts/jans/charts/persistence/Chart.yaml | 21 - .../charts/persistence/templates/service.yaml | 19 - .../templates/user-custom-secret-envs.yaml | 13 - charts/jans/charts/scim/Chart.yaml | 23 - charts/jans/charts/scim/templates/service.yml | 19 - .../templates/user-custom-secret-envs.yaml | 13 - helm/.gitignore | 114 + helm/LICENSE | 201 ++ helm/MANIFEST.in | 3 + helm/Makefile | 16 + helm/README.md | 54 + helm/docs/Makefile | 20 + helm/docs/README.md | 9 + helm/docs/conf.py | 77 + helm/docs/helm.rst | 11 + helm/docs/helpers.rst | 26 + helm/docs/index.rst | 31 + helm/docs/kubeapi.rst | 11 + helm/docs/kustomize.rst | 11 + helm/docs/make.bat | 35 + helm/docs/pycert.rst | 7 + helm/docs/settings.rst | 11 + helm/docs/terminal.rst | 130 + helm/docs/yamlparser.rst | 9 + helm/pygluu/__init__.py | 6 + helm/pygluu/kubernetes/__init__.py | 7 + helm/pygluu/kubernetes/couchbase.py | 704 ++++ helm/pygluu/kubernetes/create.py | 148 + helm/pygluu/kubernetes/gluu.py | 275 ++ helm/pygluu/kubernetes/helpers.py | 208 ++ helm/pygluu/kubernetes/kubeapi.py | 497 +++ helm/pygluu/kubernetes/mysql.py | 49 + helm/pygluu/kubernetes/postgres.py | 70 + helm/pygluu/kubernetes/pycert.py | 181 ++ helm/pygluu/kubernetes/redis.py | 54 + helm/pygluu/kubernetes/settings.py | 128 + helm/pygluu/kubernetes/templates/LICENSE | 201 ++ .../kubernetes/templates/alb/ingress.yaml | 117 + .../couchbase/backup/couchbase-backup.yaml | 18 + .../couchbase/couchbase-buckets.yaml | 50 + .../couchbase/couchbase-cluster.yaml | 173 + .../couchbase-ephemeral-buckets.yaml | 47 + .../templates/couchbase/couchbase-group.yaml | 59 + .../couchbase/couchbase-rolebinding.yaml | 11 + .../templates/couchbase/couchbase-user.yaml | 10 + .../templates/couchbase/storageclasses.yaml | 15 + .../kubernetes/templates/gluu_versions.json | 62 + .../templates/helm/artifacthub-repo.yml | 1 + .../kubernetes/templates/helm/gluu/Chart.yaml | 114 + .../kubernetes/templates/helm/gluu/README.md | 651 ++++ .../helm/gluu/charts/admin-ui}/.helmignore | 0 .../helm/gluu/charts/admin-ui/Chart.yaml | 21 + .../helm/gluu/charts/admin-ui/README.md | 61 + .../charts/admin-ui}/templates/_helpers.tpl | 16 +- .../templates/admin-ui-destination-rules.yaml | 23 + .../templates/admin-ui-virtual-services.yaml | 33 + .../charts/admin-ui/templates/deployment.yml | 143 + .../gluu/charts/admin-ui/templates/hpa.yaml | 38 + .../charts/admin-ui/templates/service.yml | 30 + .../templates/user-custom-secret-envs.yaml | 22 + .../helm/gluu/charts/admin-ui/values.yaml | 82 + .../auth-server-key-rotation}/.helmignore | 0 .../auth-server-key-rotation/Chart.yaml | 20 + .../charts/auth-server-key-rotation/README.md | 14 +- .../templates/_helpers.tpl | 0 .../templates/cronjobs.yaml | 25 +- .../templates/service.yaml | 25 + .../templates/user-custom-secret-envs.yaml | 22 + .../auth-server-key-rotation/values.yaml | 11 +- .../helm/gluu/charts/auth-server}/.helmignore | 0 .../helm/gluu/charts/auth-server/Chart.yaml | 22 + .../helm/gluu}/charts/auth-server/README.md | 22 +- .../charts/auth-server/templates/_helpers.tpl | 0 .../auth-server-destination-rules.yaml | 24 + .../auth-server-virtual-services.yaml | 12 +- .../auth-server/templates/deployment.yml | 39 +- .../charts/auth-server/templates/hpa.yaml | 12 +- .../charts/auth-server/templates/service.yml | 31 + .../templates/user-custom-secret-envs.yaml | 23 + .../helm/gluu}/charts/auth-server/values.yaml | 21 +- .../helm/gluu/charts/casa}/.helmignore | 0 .../helm/gluu/charts/casa/Chart.yaml | 23 + .../templates/helm/gluu/charts/casa/README.md | 68 + .../gluu/charts/casa/templates/_helpers.tpl | 79 + .../templates/casa-destination-rules.yaml | 24 + .../casa/templates/casa-virtual-services.yaml | 35 + .../charts/casa/templates/deployment.yaml | 150 + .../helm/gluu/charts/casa/templates/hpa.yaml | 39 + .../gluu/charts/casa/templates/service.yaml | 32 + .../templates/user-custom-secret-envs.yaml | 23 + .../helm/gluu/charts/casa/values.yaml | 100 + .../helm/gluu/charts/client-api}/.helmignore | 0 .../helm/gluu}/charts/client-api/Chart.yaml | 16 +- .../helm/gluu}/charts/client-api/README.md | 18 +- .../charts/client-api/templates/_helpers.tpl | 0 .../client-api-destination-rules.yaml | 12 +- .../client-api/templates/deployment.yaml | 30 +- .../charts/client-api/templates/hpa.yaml | 12 +- .../client-api/templates/networkpolicy.yaml | 10 + .../charts/client-api/templates/service.yaml | 31 + .../templates/user-custom-secret-envs.yaml | 23 + .../helm/gluu}/charts/client-api/values.yaml | 27 +- .../gluu/charts/cn-istio-ingress}/.helmignore | 0 .../gluu/charts/cn-istio-ingress/Chart.yaml | 21 + .../gluu/charts/cn-istio-ingress/README.md | 25 + .../cn-istio-ingress/templates/_helpers.tpl | 0 .../cn-istio-ingress/templates/gateway.yaml | 8 + .../gluu}/charts/cn-istio-ingress/values.yaml | 0 .../helm/gluu/charts/config-api}/.helmignore | 0 .../helm/gluu}/charts/config-api/Chart.yaml | 16 +- .../helm/gluu}/charts/config-api/README.md | 20 +- .../charts/config-api/templates/_helpers.tpl | 0 .../config-api-destination-rules.yaml | 12 +- .../config-api/templates/deployment.yaml | 68 +- .../charts/config-api/templates/hpa.yaml | 12 +- .../charts/config-api/templates/service.yaml | 31 + .../helm/gluu}/charts/config-api/values.yaml | 23 +- .../helm/gluu}/charts/config/.helmignore | 0 .../helm/gluu/charts/config/Chart.yaml | 22 + .../helm/gluu}/charts/config/README.md | 59 +- .../gluu/charts/config/templates/_helpers.tpl | 97 + .../config/templates/clusterrolebinding.yaml | 16 +- .../charts/config/templates/configmaps.yaml | 130 +- .../config/templates/load-init-config.yml | 18 +- .../charts/config/templates/rolebinding.yaml | 12 +- .../gluu}/charts/config/templates/roles.yaml | 12 +- .../gluu/charts/config/templates/secrets.yaml | 195 ++ .../charts/config/templates/service.yaml | 14 +- .../templates/upgrade-ldap-101-jans.yaml | 1778 +++++++++++ .../config/templates/user-custom-envs.yaml | 66 + .../helm/gluu}/charts/config/values.yaml | 63 +- .../helm/gluu/charts/cr-rotate}/.helmignore | 0 .../helm/gluu/charts/cr-rotate/Chart.yaml | 21 + .../helm/gluu/charts/cr-rotate/README.md | 55 + .../charts/cr-rotate/templates/_helpers.tpl | 69 + .../charts/cr-rotate/templates/daemonset.yaml | 83 + .../charts/cr-rotate/templates/service.yaml | 34 + .../templates/user-custom-secret-envs.yaml | 23 + .../helm/gluu/charts/cr-rotate/values.yaml | 61 + .../helm/gluu/charts/fido2}/.helmignore | 0 .../helm/gluu}/charts/fido2/Chart.yaml | 18 +- .../helm/gluu}/charts/fido2/README.md | 18 +- .../gluu}/charts/fido2/templates/_helpers.tpl | 0 .../charts/fido2/templates/deployment.yml | 29 +- .../templates/fido2-destination-rules.yaml | 12 +- .../templates/fido2-virtual-services.yaml | 12 +- .../gluu}/charts/fido2/templates/hpa.yaml | 12 +- .../gluu/charts/fido2/templates/service.yml | 31 + .../templates/user-custom-secret-envs.yaml | 23 + .../helm/gluu}/charts/fido2/values.yaml | 17 +- .../helm/gluu/charts/jackrabbit}/.helmignore | 0 .../helm/gluu/charts/jackrabbit/Chart.yaml | 23 + .../helm/gluu/charts/jackrabbit/README.md | 79 + .../charts/jackrabbit/templates/_helpers.tpl | 83 + .../gluu/charts/jackrabbit/templates/hpa.yaml | 38 + .../jackrabbit-destination-rules.yaml | 23 + .../charts/jackrabbit/templates/secret.yaml | 37 + .../charts/jackrabbit/templates/service.yaml | 28 + .../jackrabbit/templates/statefulset.yaml | 117 + .../jackrabbit/templates/storageclass.yaml | 58 + .../templates/user-custom-secret-envs.yaml | 22 + .../helm/gluu/charts/jackrabbit/values.yaml | 116 + .../gluu/charts/nginx-ingress/.helmignore | 21 + .../gluu}/charts/nginx-ingress/Chart.yaml | 16 +- .../helm/gluu}/charts/nginx-ingress/README.md | 14 +- .../nginx-ingress/templates/_helpers.tpl | 0 .../nginx-ingress/templates/ingress.yaml | 98 +- .../gluu}/charts/nginx-ingress/values.yaml | 6 +- .../helm/gluu/charts/opendj/.helmignore | 21 + .../helm/gluu}/charts/opendj/Chart.yaml | 18 +- .../helm/gluu}/charts/opendj/README.md | 26 +- .../charts/opendj/templates/_helpers.tpl | 0 .../charts/opendj/templates/configmaps.yaml | 21 + .../charts/opendj/templates/cronjobs.yaml | 101 + .../gluu}/charts/opendj/templates/hpa.yaml | 11 +- .../templates/opendj-destination-rules.yaml | 11 +- .../gluu/charts/opendj/templates/secrets.yaml | 20 + .../charts/opendj/templates/service.yaml | 9 +- .../charts/opendj/templates/statefulset.yaml | 31 +- .../charts/opendj/templates/storageclass.yaml | 14 +- .../templates/user-custom-secret-envs.yaml | 22 + .../helm/gluu}/charts/opendj/values.yaml | 23 +- .../helm/gluu/charts/oxpassport/.helmignore | 21 + .../helm/gluu/charts/oxpassport/Chart.yaml | 23 + .../helm/gluu/charts/oxpassport/README.md | 69 + .../charts/oxpassport/templates/_helpers.tpl | 68 + .../oxpassport/templates/deployment.yaml | 148 + .../gluu/charts/oxpassport/templates/hpa.yaml | 38 + .../oxpassport-destination-rules.yaml | 23 + .../oxpassport-virtual-services.yaml | 34 + .../charts/oxpassport/templates/service.yaml | 31 + .../templates/user-custom-secret-envs.yaml | 22 + .../helm/gluu/charts/oxpassport/values.yaml | 98 + .../helm/gluu/charts/oxshibboleth/.helmignore | 21 + .../helm/gluu/charts/oxshibboleth/Chart.yaml | 22 + .../helm/gluu/charts/oxshibboleth/README.md | 70 + .../oxshibboleth/templates/_helpers.tpl | 68 + .../charts/oxshibboleth/templates/hpa.yaml | 39 + .../oxshibboleth-destination-rules.yaml | 24 + .../oxshibboleth-virtual-services.yaml | 37 + .../oxshibboleth/templates/service.yaml | 35 + .../oxshibboleth/templates/statefulset.yaml | 146 + .../templates/user-custom-secret-envs.yaml | 23 + .../helm/gluu/charts/oxshibboleth/values.yaml | 97 + .../helm/gluu/charts/persistence/.helmignore | 22 + .../helm/gluu/charts/persistence/Chart.yaml | 21 + .../helm/gluu}/charts/persistence/README.md | 16 +- .../charts/persistence/templates/_helpers.tpl | 0 .../charts/persistence/templates/jobs.yml | 42 +- .../charts/persistence/templates/service.yaml | 27 + .../templates/user-custom-secret-envs.yaml | 22 + .../helm/gluu}/charts/persistence/values.yaml | 11 +- .../helm/gluu/charts/scim/.helmignore | 21 + .../helm/gluu/charts/scim/Chart.yaml | 23 + .../helm/gluu}/charts/scim/README.md | 18 +- .../gluu}/charts/scim/templates/_helpers.tpl | 0 .../charts/scim/templates/deployment.yml | 41 +- .../helm/gluu}/charts/scim/templates/hpa.yaml | 12 +- .../templates/scim-destination-rules.yaml | 12 +- .../scim/templates/scim-virtual-services.yaml | 12 +- .../gluu/charts/scim/templates/service.yml | 31 + .../templates/user-custom-secret-envs.yaml | 23 + .../helm/gluu}/charts/scim/values.yaml | 15 +- .../templates/helm/gluu/openbanking-helm.md | 194 +- .../helm/gluu/openbanking-values.yaml | 607 +--- .../helm/gluu}/templates/_helpers.tpl | 0 .../templates/helm/gluu}/values.schema.json | 432 ++- .../templates/helm/gluu/values.yaml | 1654 ++++++++++ .../templates/ldap/base/101-ox.yaml | 2833 +++++++++++++++++ helm/pygluu/kubernetes/terminal/__init__.py | 0 .../kubernetes/terminal/architecture.py | 53 + helm/pygluu/kubernetes/terminal/aws.py | 63 + helm/pygluu/kubernetes/terminal/backup.py | 56 + helm/pygluu/kubernetes/terminal/cache.py | 41 + .../kubernetes/terminal/configuration.py | 98 + .../kubernetes/terminal/confirmsettings.py | 43 + helm/pygluu/kubernetes/terminal/couchbase.py | 197 ++ .../kubernetes/terminal/distribution.py | 36 + helm/pygluu/kubernetes/terminal/gke.py | 36 + helm/pygluu/kubernetes/terminal/google.py | 72 + helm/pygluu/kubernetes/terminal/helm.py | 106 + helm/pygluu/kubernetes/terminal/helpers.py | 125 + helm/pygluu/kubernetes/terminal/images.py | 71 + helm/pygluu/kubernetes/terminal/istio.py | 48 + helm/pygluu/kubernetes/terminal/jackrabbit.py | 72 + helm/pygluu/kubernetes/terminal/ldap.py | 45 + helm/pygluu/kubernetes/terminal/license.py | 35 + helm/pygluu/kubernetes/terminal/namespace.py | 24 + .../pygluu/kubernetes/terminal/openbanking.py | 130 + .../kubernetes/terminal/optionalservices.py | 69 + .../kubernetes/terminal/persistencebackend.py | 47 + helm/pygluu/kubernetes/terminal/postgres.py | 58 + helm/pygluu/kubernetes/terminal/prompt.py | 236 ++ helm/pygluu/kubernetes/terminal/redis.py | 58 + helm/pygluu/kubernetes/terminal/replicas.py | 52 + helm/pygluu/kubernetes/terminal/sql.py | 80 + helm/pygluu/kubernetes/terminal/testenv.py | 30 + helm/pygluu/kubernetes/terminal/upgrade.py | 39 + helm/pygluu/kubernetes/terminal/version.py | 38 + helm/pygluu/kubernetes/terminal/volumes.py | 118 + helm/pygluu/kubernetes/yamlparser.py | 114 + helm/settings_schema.json | 1193 +++++++ helm/setup.py | 66 + helm/tests/conftest.py | 10 + helm/tests/terminal/test_architecture.py | 22 + helm/tests/terminal/test_aws.py | 28 + helm/tests/terminal/test_backup.py | 115 + helm/tests/terminal/test_cache.py | 19 + helm/tests/terminal/test_configuration.py | 223 ++ helm/tests/terminal/test_confirmsettings.py | 21 + helm/tests/terminal/test_couchbase.py | 122 + helm/tests/terminal/test_distribution.py | 16 + helm/tests/terminal/test_gke.py | 30 + helm/tests/terminal/test_helm.py | 72 + helm/tests/terminal/test_helpers.py | 55 + helm/tests/terminal/test_images.py | 114 + helm/tests/terminal/test_istio.py | 67 + helm/tests/terminal/test_jackrabbit.py | 100 + helm/tests/terminal/test_ldap.py | 20 + helm/tests/terminal/test_license.py | 19 + helm/tests/terminal/test_namespace.py | 15 + helm/tests/terminal/test_openbanking.py | 30 + helm/tests/terminal/test_optionalservices.py | 141 + .../tests/terminal/test_persistencebackend.py | 29 + helm/tests/terminal/test_postgres.py | 45 + helm/tests/terminal/test_prompt.py | 208 ++ helm/tests/terminal/test_redis.py | 43 + helm/tests/terminal/test_replicas.py | 103 + helm/tests/terminal/test_testenv.py | 14 + helm/tests/terminal/test_upgrade.py | 20 + helm/tests/terminal/test_version.py | 51 + helm/tests/terminal/test_volumes.py | 179 ++ helm/tests/test_create.py | 27 + helm/tests/test_gluu.py | 0 helm/tests/test_gluucouchbase.py | 30 + helm/tests/test_gluuhelpers.py | 0 helm/tests/test_gluukubeapi.py | 0 helm/tests/test_gluupostgres.py | 9 + helm/tests/test_gluupycert.py | 36 + helm/tests/test_gluuredis.py | 9 + helm/tests/test_settings.py | 32 + helm/tests/test_yamlparser.py | 0 helm/tox.ini | 17 + 342 files changed, 22519 insertions(+), 2306 deletions(-) delete mode 100644 automation/startdemo.sh create mode 100644 automation/startopenabankingdemo.sh delete mode 100644 charts/artifacthub-repo.yml delete mode 100644 charts/index.yaml delete mode 100644 charts/jans-1.0.0-a4.tgz delete mode 100644 charts/jans-1.0.0-b10.tgz delete mode 100644 charts/jans-1.0.0-b11.tgz delete mode 100644 charts/jans-1.0.0-b6.tgz delete mode 100644 charts/jans-1.0.0-b7.tgz delete mode 100644 charts/jans-1.0.0-b8.tgz delete mode 100644 charts/jans-1.0.0-b9.tgz delete mode 100644 charts/jans/Chart.yaml delete mode 100644 charts/jans/charts/auth-server-key-rotation/Chart.yaml delete mode 100644 charts/jans/charts/auth-server-key-rotation/templates/service.yaml delete mode 100644 charts/jans/charts/auth-server-key-rotation/templates/user-custom-secret-envs.yaml delete mode 100644 charts/jans/charts/auth-server/Chart.yaml delete mode 100644 charts/jans/charts/auth-server/templates/auth-server-destination-rules.yaml delete mode 100644 charts/jans/charts/auth-server/templates/service.yml delete mode 100644 charts/jans/charts/auth-server/templates/user-custom-secret-envs.yaml delete mode 100644 charts/jans/charts/client-api/templates/service.yaml delete mode 100644 charts/jans/charts/client-api/templates/user-custom-secret-envs.yaml delete mode 100644 charts/jans/charts/cn-istio-ingress/Chart.yaml delete mode 100644 charts/jans/charts/cn-istio-ingress/README.md delete mode 100644 charts/jans/charts/config-api/templates/service.yaml delete mode 100644 charts/jans/charts/config/Chart.yaml delete mode 100644 charts/jans/charts/config/templates/secrets.yaml delete mode 100644 charts/jans/charts/config/templates/user-custom-envs.yaml delete mode 100644 charts/jans/charts/fido2/templates/service.yml delete mode 100644 charts/jans/charts/fido2/templates/user-custom-secret-envs.yaml delete mode 100644 charts/jans/charts/opendj/templates/configmaps.yaml delete mode 100644 charts/jans/charts/opendj/templates/secrets.yaml delete mode 100644 charts/jans/charts/opendj/templates/user-custom-secret-envs.yaml delete mode 100644 charts/jans/charts/persistence/Chart.yaml delete mode 100644 charts/jans/charts/persistence/templates/service.yaml delete mode 100644 charts/jans/charts/persistence/templates/user-custom-secret-envs.yaml delete mode 100644 charts/jans/charts/scim/Chart.yaml delete mode 100644 charts/jans/charts/scim/templates/service.yml delete mode 100644 charts/jans/charts/scim/templates/user-custom-secret-envs.yaml create mode 100644 helm/.gitignore create mode 100644 helm/LICENSE create mode 100644 helm/MANIFEST.in create mode 100644 helm/Makefile create mode 100644 helm/README.md create mode 100644 helm/docs/Makefile create mode 100644 helm/docs/README.md create mode 100644 helm/docs/conf.py create mode 100644 helm/docs/helm.rst create mode 100644 helm/docs/helpers.rst create mode 100644 helm/docs/index.rst create mode 100644 helm/docs/kubeapi.rst create mode 100644 helm/docs/kustomize.rst create mode 100644 helm/docs/make.bat create mode 100644 helm/docs/pycert.rst create mode 100644 helm/docs/settings.rst create mode 100644 helm/docs/terminal.rst create mode 100644 helm/docs/yamlparser.rst create mode 100644 helm/pygluu/__init__.py create mode 100644 helm/pygluu/kubernetes/__init__.py create mode 100644 helm/pygluu/kubernetes/couchbase.py create mode 100644 helm/pygluu/kubernetes/create.py create mode 100644 helm/pygluu/kubernetes/gluu.py create mode 100644 helm/pygluu/kubernetes/helpers.py create mode 100644 helm/pygluu/kubernetes/kubeapi.py create mode 100644 helm/pygluu/kubernetes/mysql.py create mode 100644 helm/pygluu/kubernetes/postgres.py create mode 100644 helm/pygluu/kubernetes/pycert.py create mode 100644 helm/pygluu/kubernetes/redis.py create mode 100644 helm/pygluu/kubernetes/settings.py create mode 100644 helm/pygluu/kubernetes/templates/LICENSE create mode 100644 helm/pygluu/kubernetes/templates/alb/ingress.yaml create mode 100644 helm/pygluu/kubernetes/templates/couchbase/backup/couchbase-backup.yaml create mode 100644 helm/pygluu/kubernetes/templates/couchbase/couchbase-buckets.yaml create mode 100644 helm/pygluu/kubernetes/templates/couchbase/couchbase-cluster.yaml create mode 100644 helm/pygluu/kubernetes/templates/couchbase/couchbase-ephemeral-buckets.yaml create mode 100644 helm/pygluu/kubernetes/templates/couchbase/couchbase-group.yaml create mode 100644 helm/pygluu/kubernetes/templates/couchbase/couchbase-rolebinding.yaml create mode 100644 helm/pygluu/kubernetes/templates/couchbase/couchbase-user.yaml create mode 100644 helm/pygluu/kubernetes/templates/couchbase/storageclasses.yaml create mode 100644 helm/pygluu/kubernetes/templates/gluu_versions.json create mode 100644 helm/pygluu/kubernetes/templates/helm/artifacthub-repo.yml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/Chart.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/README.md rename {charts/jans/charts/auth-server-key-rotation => helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui}/.helmignore (100%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/Chart.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/README.md rename {charts/jans/charts/config => helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui}/templates/_helpers.tpl (83%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/admin-ui-destination-rules.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/admin-ui-virtual-services.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/deployment.yml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/hpa.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/service.yml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/user-custom-secret-envs.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/values.yaml rename {charts/jans/charts/auth-server => helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation}/.helmignore (100%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/Chart.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/auth-server-key-rotation/README.md (60%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/auth-server-key-rotation/templates/_helpers.tpl (100%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/auth-server-key-rotation/templates/cronjobs.yaml (83%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/templates/service.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/templates/user-custom-secret-envs.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/auth-server-key-rotation/values.yaml (72%) rename {charts/jans/charts/client-api => helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server}/.helmignore (100%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/Chart.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/auth-server/README.md (62%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/auth-server/templates/_helpers.tpl (100%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/auth-server-destination-rules.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/auth-server/templates/auth-server-virtual-services.yaml (87%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/auth-server/templates/deployment.yml (93%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/auth-server/templates/hpa.yaml (68%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/service.yml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/user-custom-secret-envs.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/auth-server/values.yaml (65%) rename {charts/jans/charts/cn-istio-ingress => helm/pygluu/kubernetes/templates/helm/gluu/charts/casa}/.helmignore (100%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/Chart.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/README.md create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/_helpers.tpl create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/casa-destination-rules.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/casa-virtual-services.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/deployment.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/hpa.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/service.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/user-custom-secret-envs.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/values.yaml rename {charts/jans/charts/config-api => helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api}/.helmignore (100%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/client-api/Chart.yaml (64%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/client-api/README.md (64%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/client-api/templates/_helpers.tpl (100%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/client-api/templates/client-api-destination-rules.yaml (53%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/client-api/templates/deployment.yaml (87%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/client-api/templates/hpa.yaml (68%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/client-api/templates/networkpolicy.yaml (67%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/service.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/user-custom-secret-envs.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/client-api/values.yaml (70%) rename {charts/jans/charts/persistence => helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress}/.helmignore (100%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/Chart.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/README.md rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/cn-istio-ingress/templates/_helpers.tpl (100%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/cn-istio-ingress/templates/gateway.yaml (68%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/cn-istio-ingress/values.yaml (100%) rename {charts/jans/charts/fido2 => helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api}/.helmignore (100%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config-api/Chart.yaml (57%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config-api/README.md (65%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config-api/templates/_helpers.tpl (100%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config-api/templates/config-api-destination-rules.yaml (53%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config-api/templates/deployment.yaml (69%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config-api/templates/hpa.yaml (68%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/service.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config-api/values.yaml (65%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config/.helmignore (100%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/config/Chart.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config/README.md (65%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/_helpers.tpl rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config/templates/clusterrolebinding.yaml (61%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config/templates/configmaps.yaml (69%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config/templates/load-init-config.yml (89%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config/templates/rolebinding.yaml (58%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config/templates/roles.yaml (51%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/secrets.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config/templates/service.yaml (51%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/upgrade-ldap-101-jans.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/user-custom-envs.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/config/values.yaml (75%) rename {charts/jans/charts/nginx-ingress => helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate}/.helmignore (100%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/Chart.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/README.md create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/_helpers.tpl create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/daemonset.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/service.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/user-custom-secret-envs.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/values.yaml rename {charts/jans/charts/opendj => helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2}/.helmignore (100%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/fido2/Chart.yaml (54%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/fido2/README.md (67%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/fido2/templates/_helpers.tpl (100%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/fido2/templates/deployment.yml (88%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/fido2/templates/fido2-destination-rules.yaml (51%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/fido2/templates/fido2-virtual-services.yaml (67%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/fido2/templates/hpa.yaml (68%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/service.yml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/user-custom-secret-envs.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/fido2/values.yaml (72%) rename {charts/jans/charts/scim => helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit}/.helmignore (100%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/Chart.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/README.md create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/_helpers.tpl create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/hpa.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/jackrabbit-destination-rules.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/secret.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/service.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/statefulset.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/storageclass.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/user-custom-secret-envs.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/values.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/.helmignore rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/nginx-ingress/Chart.yaml (50%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/nginx-ingress/README.md (83%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/nginx-ingress/templates/_helpers.tpl (100%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/nginx-ingress/templates/ingress.yaml (84%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/nginx-ingress/values.yaml (95%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/.helmignore rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/opendj/Chart.yaml (56%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/opendj/README.md (75%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/opendj/templates/_helpers.tpl (100%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/configmaps.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/cronjobs.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/opendj/templates/hpa.yaml (70%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/opendj/templates/opendj-destination-rules.yaml (59%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/secrets.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/opendj/templates/service.yaml (91%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/opendj/templates/statefulset.yaml (84%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/opendj/templates/storageclass.yaml (86%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/user-custom-secret-envs.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/opendj/values.yaml (82%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/.helmignore create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/Chart.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/README.md create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/_helpers.tpl create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/deployment.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/hpa.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/oxpassport-destination-rules.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/oxpassport-virtual-services.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/service.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/user-custom-secret-envs.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/values.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/.helmignore create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/Chart.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/README.md create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/_helpers.tpl create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/hpa.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/oxshibboleth-destination-rules.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/oxshibboleth-virtual-services.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/service.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/statefulset.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/user-custom-secret-envs.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/values.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/.helmignore create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/Chart.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/persistence/README.md (61%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/persistence/templates/_helpers.tpl (100%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/persistence/templates/jobs.yml (78%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/templates/service.yaml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/templates/user-custom-secret-envs.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/persistence/values.yaml (69%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/.helmignore create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/Chart.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/scim/README.md (66%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/scim/templates/_helpers.tpl (100%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/scim/templates/deployment.yml (83%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/scim/templates/hpa.yaml (68%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/scim/templates/scim-destination-rules.yaml (51%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/scim/templates/scim-virtual-services.yaml (75%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/service.yml create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/user-custom-secret-envs.yaml rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/charts/scim/values.yaml (71%) rename charts/jans/README.md => helm/pygluu/kubernetes/templates/helm/gluu/openbanking-helm.md (55%) rename charts/jans/values.yaml => helm/pygluu/kubernetes/templates/helm/gluu/openbanking-values.yaml (56%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/templates/_helpers.tpl (100%) rename {charts/jans => helm/pygluu/kubernetes/templates/helm/gluu}/values.schema.json (86%) create mode 100644 helm/pygluu/kubernetes/templates/helm/gluu/values.yaml create mode 100644 helm/pygluu/kubernetes/templates/ldap/base/101-ox.yaml create mode 100644 helm/pygluu/kubernetes/terminal/__init__.py create mode 100644 helm/pygluu/kubernetes/terminal/architecture.py create mode 100644 helm/pygluu/kubernetes/terminal/aws.py create mode 100644 helm/pygluu/kubernetes/terminal/backup.py create mode 100644 helm/pygluu/kubernetes/terminal/cache.py create mode 100644 helm/pygluu/kubernetes/terminal/configuration.py create mode 100644 helm/pygluu/kubernetes/terminal/confirmsettings.py create mode 100644 helm/pygluu/kubernetes/terminal/couchbase.py create mode 100644 helm/pygluu/kubernetes/terminal/distribution.py create mode 100644 helm/pygluu/kubernetes/terminal/gke.py create mode 100644 helm/pygluu/kubernetes/terminal/google.py create mode 100644 helm/pygluu/kubernetes/terminal/helm.py create mode 100644 helm/pygluu/kubernetes/terminal/helpers.py create mode 100644 helm/pygluu/kubernetes/terminal/images.py create mode 100644 helm/pygluu/kubernetes/terminal/istio.py create mode 100644 helm/pygluu/kubernetes/terminal/jackrabbit.py create mode 100644 helm/pygluu/kubernetes/terminal/ldap.py create mode 100644 helm/pygluu/kubernetes/terminal/license.py create mode 100644 helm/pygluu/kubernetes/terminal/namespace.py create mode 100644 helm/pygluu/kubernetes/terminal/openbanking.py create mode 100644 helm/pygluu/kubernetes/terminal/optionalservices.py create mode 100644 helm/pygluu/kubernetes/terminal/persistencebackend.py create mode 100644 helm/pygluu/kubernetes/terminal/postgres.py create mode 100644 helm/pygluu/kubernetes/terminal/prompt.py create mode 100644 helm/pygluu/kubernetes/terminal/redis.py create mode 100644 helm/pygluu/kubernetes/terminal/replicas.py create mode 100644 helm/pygluu/kubernetes/terminal/sql.py create mode 100644 helm/pygluu/kubernetes/terminal/testenv.py create mode 100644 helm/pygluu/kubernetes/terminal/upgrade.py create mode 100644 helm/pygluu/kubernetes/terminal/version.py create mode 100644 helm/pygluu/kubernetes/terminal/volumes.py create mode 100644 helm/pygluu/kubernetes/yamlparser.py create mode 100644 helm/settings_schema.json create mode 100755 helm/setup.py create mode 100644 helm/tests/conftest.py create mode 100644 helm/tests/terminal/test_architecture.py create mode 100644 helm/tests/terminal/test_aws.py create mode 100644 helm/tests/terminal/test_backup.py create mode 100644 helm/tests/terminal/test_cache.py create mode 100644 helm/tests/terminal/test_configuration.py create mode 100644 helm/tests/terminal/test_confirmsettings.py create mode 100644 helm/tests/terminal/test_couchbase.py create mode 100644 helm/tests/terminal/test_distribution.py create mode 100644 helm/tests/terminal/test_gke.py create mode 100644 helm/tests/terminal/test_helm.py create mode 100644 helm/tests/terminal/test_helpers.py create mode 100644 helm/tests/terminal/test_images.py create mode 100644 helm/tests/terminal/test_istio.py create mode 100644 helm/tests/terminal/test_jackrabbit.py create mode 100644 helm/tests/terminal/test_ldap.py create mode 100644 helm/tests/terminal/test_license.py create mode 100644 helm/tests/terminal/test_namespace.py create mode 100644 helm/tests/terminal/test_openbanking.py create mode 100644 helm/tests/terminal/test_optionalservices.py create mode 100644 helm/tests/terminal/test_persistencebackend.py create mode 100644 helm/tests/terminal/test_postgres.py create mode 100644 helm/tests/terminal/test_prompt.py create mode 100644 helm/tests/terminal/test_redis.py create mode 100644 helm/tests/terminal/test_replicas.py create mode 100644 helm/tests/terminal/test_testenv.py create mode 100644 helm/tests/terminal/test_upgrade.py create mode 100644 helm/tests/terminal/test_version.py create mode 100644 helm/tests/terminal/test_volumes.py create mode 100644 helm/tests/test_create.py create mode 100644 helm/tests/test_gluu.py create mode 100644 helm/tests/test_gluucouchbase.py create mode 100644 helm/tests/test_gluuhelpers.py create mode 100644 helm/tests/test_gluukubeapi.py create mode 100644 helm/tests/test_gluupostgres.py create mode 100644 helm/tests/test_gluupycert.py create mode 100644 helm/tests/test_gluuredis.py create mode 100644 helm/tests/test_settings.py create mode 100644 helm/tests/test_yamlparser.py create mode 100644 helm/tox.ini diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index aa042845886..072ff1dc699 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,7 +4,7 @@ # These owners will be the default owners for everything in this branch of # the repo. Unless a later match takes precedence -/docker-jans-*/ @moabu +/docker-jans-*/ @moabu @iromli /automation/ @moabu /charts/ @moabu /.github/ @moabu diff --git a/.github/workflows/microk8s_couchbase.yml b/.github/workflows/microk8s_couchbase.yml index 2c765a5c0c4..a8581705e8e 100644 --- a/.github/workflows/microk8s_couchbase.yml +++ b/.github/workflows/microk8s_couchbase.yml @@ -5,13 +5,13 @@ on: - master - main paths: - - "charts/**" + - "helm/**" pull_request: branches: - master - main paths: - - "charts/**" + - "helm/**" workflow_dispatch: jobs: build: diff --git a/.github/workflows/microk8s_mysql.yml b/.github/workflows/microk8s_mysql.yml index cea4e7e5a8d..4640e10199c 100644 --- a/.github/workflows/microk8s_mysql.yml +++ b/.github/workflows/microk8s_mysql.yml @@ -5,13 +5,13 @@ on: - master - main paths: - - "charts/**" + - "helm/**" pull_request: branches: - master - main paths: - - "charts/**" + - "helm/**" workflow_dispatch: jobs: build: diff --git a/.github/workflows/microk8s_opendj.yml b/.github/workflows/microk8s_opendj.yml index a10545cd276..1e0b754b889 100644 --- a/.github/workflows/microk8s_opendj.yml +++ b/.github/workflows/microk8s_opendj.yml @@ -5,13 +5,13 @@ on: - master - main paths: - - "charts/**" + - "helm/**" pull_request: branches: - master - main paths: - - "charts/**" + - "helm/**" workflow_dispatch: jobs: build: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b8446289e63..36d922e179c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -35,7 +35,7 @@ jobs: git config user.email "54212639+mo-auto@users.noreply.github.com" git config --global user.signingkey "${{ steps.import_gpg.outputs.keyid }}" - - uses: google-github-actions/release-please-action@v3 + - uses: google-github-actions/release-please-action@v3.0.1 id: release-please with: path: ${{ matrix.docker-images }} @@ -48,7 +48,7 @@ jobs: strategy: fail-fast: false matrix: - python-projects: ["jans-pycloudlib"] + python-projects: ["jans-pycloudlib", "jans-cli"] steps: - name: Checkout uses: actions/checkout@v2 @@ -70,7 +70,7 @@ jobs: git config user.email "54212639+mo-auto@users.noreply.github.com" git config --global user.signingkey "${{ steps.import_gpg.outputs.keyid }}" - - uses: google-github-actions/release-please-action@v3 + - uses: google-github-actions/release-please-action@v3.0.1 id: release-please with: path: ${{ matrix.python-projects }} diff --git a/README.md b/README.md index fe01d8ed67e..2cf24c6e5e7 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,10 @@ Start a fresh ubuntu `18.04` or `20.04` and execute the following ```bash sudo su - -wget https://raw.githubusercontent.com/JanssenProject/jans-cloud-native/master/automation/startdemo.sh && chmod u+x startdemo.sh && ./startdemo.sh +wget https://raw.githubusercontent.com/JanssenProject/jans/master/automation/startopenbankingdemo.sh && chmod u+x startopenbankingdemo.sh && ./startopenbankingdemo.sh ``` -This will install docker, microk8s, helm and Janssen with the default settings the can be found inside [values.yaml](charts/jans/values.yaml). Please map the `ip` of the instance running ubuntu to `demoexample.jans.io` and then access the endpoints at your browser such in the example in the table below. +This will install docker, microk8s, helm and Janssen with the default settings the can be found inside [values.yaml](helm/pygluu/kubernetes/templates/gluu/values.yaml). Please map the `ip` of the instance running ubuntu to `demoexample.jans.io` and then access the endpoints at your browser such in the example in the table below. | Service | Example endpoint | | ----------- | -------------------------------------------------------------- | @@ -45,4 +45,6 @@ This will install docker, microk8s, helm and Janssen with the default settings t | fido2 | `https://demoexample.jans.io/.well-known/fido2-configuration` | | scim | `https://demoexample.jans.io/.well-known/scim-configuration` | -For more information follow [here](charts/jans/README.md). +For more information follow [here](helm/README.md). + +Helm charts are located \ No newline at end of file diff --git a/_config.yml b/_config.yml index da3de5b0df8..1f4df789d14 100644 --- a/_config.yml +++ b/_config.yml @@ -7,7 +7,7 @@ buttons: href: /jans-cloud-native - b1: text: Helm Chart - href: /jans-cloud-native/charts/jans + href: /jans-cloud-native/helm/pygluu/kubernetes/helm - b2: text: Debugging Interception scripts href: /jans-cloud-native/docs/interception-script-debug diff --git a/automation/startdemo.sh b/automation/startdemo.sh deleted file mode 100644 index 503448b0985..00000000000 --- a/automation/startdemo.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -set -e -sudo apt-get update -sudo apt-get install python3-pip -y -sudo pip3 install pip --upgrade -sudo pip3 install setuptools --upgrade -sudo pip3 install pyOpenSSL --upgrade -sudo apt-get update -sudo apt-get install build-essential unzip -y -sudo pip3 install requests --upgrade -sudo pip3 install shiv -git clone https://github.com/JanssenProject/jans-cloud-native.git || [ -d "./jans-cloud-native" ] && echo "Directory exists." -cd jans-cloud-native -sudo snap install microk8s --classic -sudo microk8s.status --wait-ready -sudo microk8s.enable dns registry ingress -sudo microk8s kubectl get daemonset.apps/nginx-ingress-microk8s-controller -n ingress -o yaml | sed -s "s@ingress-class=public@ingress-class=nginx@g" | microk8s kubectl apply -f - -sudo apt-get update -sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common -y -curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - -sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" -sudo apt-get update -sudo apt-get install net-tools -curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 -chmod 700 get_helm.sh -./get_helm.sh -sudo apt-get install docker-ce docker-ce-cli containerd.io -y -microk8s.kubectl create namespace jans || echo "namespace exists" -microk8s.config > ~/.kube/config -default_iface=$(awk '$2 == 00000000 { print $1 }' /proc/net/route) -ip=$(ip addr show dev "$default_iface" | awk '$1 == "inet" { sub("/.*", "", $2); print $2 }') -helm install jans -f ./charts/jans/values.yaml ./charts/jans -n jans --set global.lbIp="$ip" || echo "Please get ip of the instance and run helm install jans -f ./jans-cloud-native/helm/values.yaml ./jans-cloud-native/helm -n jans --set global.lbIp=" diff --git a/automation/startopenabankingdemo.sh b/automation/startopenabankingdemo.sh new file mode 100644 index 00000000000..b2fb7c912b7 --- /dev/null +++ b/automation/startopenabankingdemo.sh @@ -0,0 +1,97 @@ +#!/bin/bash +set -e +sudo apt-get update +sudo apt-get install python3-pip -y +sudo pip3 install pip --upgrade +sudo pip3 install setuptools --upgrade +sudo pip3 install pyOpenSSL --upgrade +sudo apt-get update +sudo apt-get install build-essential unzip -y +sudo pip3 install requests --upgrade +sudo pip3 install shiv +sudo snap install microk8s --classic +sudo microk8s.status --wait-ready +sudo microk8s.enable dns registry ingress +sudo microk8s kubectl get daemonset.apps/nginx-ingress-microk8s-controller -n ingress -o yaml | sed -s "s@ingress-class=public@ingress-class=nginx@g" | microk8s kubectl apply -f - +sudo apt-get update +sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common -y +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - +sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" +sudo apt-get update +sudo apt-get install net-tools +curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 +chmod 700 get_helm.sh +./get_helm.sh +sudo apt-get install docker-ce docker-ce-cli containerd.io -y +sudo microk8s config > config +KUBECONFIG="$PWD"/config +sudo microk8s.kubectl create namespace gluu --kubeconfig="$KUBECONFIG" || echo "namespace exists" +sudo helm repo add bitnami https://charts.bitnami.com/bitnami +sudo microk8s.kubectl get po --kubeconfig="$KUBECONFIG" +sudo helm install my-release --set auth.rootPassword=Test1234#,auth.database=jans bitnami/mysql -n gluu --kubeconfig="$KUBECONFIG" +EXT_IP=$(dig +short myip.opendns.com @resolver1.opendns.com) +sudo echo "$EXT_IP demoexample.gluu.org" >> /etc/hosts +cat << EOF > override.yaml +config: + configmap: + cnSqlDbHost: my-release-mysql.gluu.svc + cnSqlDbUser: root +nginx-ingress: + ingress: + #/jans-auth/restv1/token + authServerProtectedToken: true + #/jans-auth/restv1/register + authServerProtectedRegister: true + # in the format of {cert-manager.io/cluster-issuer: nameOfClusterIssuer, kubernetes.io/tls-acme: "true"} + additionalAnnotations: + # Enable client certificate authentication + nginx.ingress.kubernetes.io/auth-tls-verify-client: "optional" + # Create the secret containing the trusted ca certificates + nginx.ingress.kubernetes.io/auth-tls-secret: "gluu/ca-secret" + # Specify the verification depth in the client certificates chain + nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1" + # Specify if certificates are passed to upstream server + nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true" +global: + isFqdnRegistered: false + lbIp: $EXT_IP +EOF +sudo helm repo add gluu https://gluufederation.github.io/cloud-native-edition/pygluu/kubernetes/templates/helm +sudo helm repo update +sudo helm install gluu gluu/gluu -n gluu --version=5.0.2 -f override.yaml --kubeconfig="$KUBECONFIG" +echo "Waiting for auth-server to come up....Please do not cancel out...This will wait for the auth-server to be ready.." +sleep 120 +cat << EOF > testendpoints.sh +# get certs and keys. This will also generate the client crt and key to be used to access protected endpoints +mkdir quicktestcerts || echo "directory exists" +cd quicktestcerts +sudo microk8s config > config +KUBECONFIG="$PWD"/config +rm ca.crt ca.key server.crt server.key client.csr client.crt client.key +sudo microk8s.kubectl delete secret generic ca-secret -n gluu --kubeconfig="$KUBECONFIG" || echo "secret ca-secret does not exist and will be created." +sudo microk8s.kubectl get secret cn -o json -n gluu --kubeconfig="$KUBECONFIG" | grep '"ssl_ca_cert":' | sed -e 's#.*:\(\)#\1#' | tr -d '"' | tr -d "," | tr -d '[:space:]' | base64 -d > ca.crt +sudo microk8s.kubectl get secret cn -o json -n gluu --kubeconfig="$KUBECONFIG" | grep '"ssl_ca_key":' | sed -e 's#.*:\(\)#\1#' | tr -d '"' | tr -d "," | tr -d '[:space:]' | base64 -d > ca.key +sudo microk8s.kubectl get secret cn -o json -n gluu --kubeconfig="$KUBECONFIG" | grep '"ssl_cert":' | sed -e 's#.*:\(\)#\1#' | tr -d '"' | tr -d "," | tr -d '[:space:]' | base64 -d > server.crt +sudo microk8s.kubectl get secret cn -o json -n gluu --kubeconfig="$KUBECONFIG" | grep '"ssl_key":' | sed -e 's#.*:\(\)#\1#' | tr -d '"' | tr -d "," | tr -d '[:space:]' | base64 -d > server.key +openssl req -new -newkey rsa:4096 -keyout client.key -out client.csr -nodes -subj '/CN=Openbanking' +openssl x509 -req -sha256 -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 02 -out client.crt +sudo microk8s.kubectl create secret generic ca-secret -n gluu --from-file=tls.crt=server.crt --from-file=tls.key=server.key --from-file=ca.crt=ca.crt +echo -e "Starting simple test to endpoints. \n" +sleep 10 +echo -e "Testing openid-configuration endpoint.. \n" +curl -k https://demoexample.gluu.org/.well-known/openid-configuration +TESTCLIENT=$(microk8s.kubectl get cm cn -o json -n gluu --kubeconfig="$KUBECONFIG" | grep '"jca_client_id":' | sed -e 's#.*:\(\)#\1#' | tr -d '"' | tr -d "," | tr -d '[:space:]') +TESTCLIENTSECRET=$(microk8s.kubectl get secret cn -o json -n gluu --kubeconfig="$KUBECONFIG" | grep '"jca_client_pw":' | sed -e 's#.*:\(\)#\1#' | tr -d '"' | tr -d "," | tr -d '[:space:]' | base64 -d) +echo -e "Testing protected endpoint /token without client crt and key. This should show a 403, showing mTLS works \n" +curl -X POST -k -u $TESTCLIENT:$TESTCLIENTSECRET https://demoexample.gluu.org/jans-auth/restv1/token -d grant_type=client_credentials +echo -e "Testing protected endpoint /token with client crt and key. This should recieve a token, showing mTLS works \n" +curl -X POST -k --cert client.crt --key client.key -u $TESTCLIENT:$TESTCLIENTSECRET https://demoexample.gluu.org/jans-auth/restv1/token -d grant_type=client_credentials +echo -e "Testing protected endpoint /register without client crt and key. This should show a 403, showing mTLS works \n" +curl -X POST -k -u $TESTCLIENT:$TESTCLIENTSECRET https://demoexample.gluu.org/jans-auth/restv1/register +echo -e "Testing protected endpoint /register with client crt and key. This should still recieve an error but from the AS showing mTLS works \n" +curl -X POST -k --cert client.crt --key client.key -u $TESTCLIENT:$TESTCLIENTSECRET https://demoexample.gluu.org/jans-auth/restv1/register +cd .. +EOF +sudo microk8s.kubectl -n gluu wait --for=condition=available --timeout=600s deploy/gluu-auth-server --kubeconfig="$KUBECONFIG" +sudo bash testendpoints.sh +echo -e "You may re-execute bash testendpoints.sh to do a quick test to protected endpoints and openid-configuration endpoint." diff --git a/charts/artifacthub-repo.yml b/charts/artifacthub-repo.yml deleted file mode 100644 index 51fd13db3c9..00000000000 --- a/charts/artifacthub-repo.yml +++ /dev/null @@ -1 +0,0 @@ -repositoryID: e47c2a74-ef30-4535-ab6e-bdd5fa03d2b1 \ No newline at end of file diff --git a/charts/index.yaml b/charts/index.yaml deleted file mode 100644 index 13411a85a18..00000000000 --- a/charts/index.yaml +++ /dev/null @@ -1,596 +0,0 @@ -apiVersion: v1 -entries: - jans: - - annotations: - artifacthub.io/changes: | - - Janssen 1.0 under dev charts - artifacthub.io/containsSecurityUpdates: "true" - artifacthub.io/images: | - - name: auth-server - image: janssenproject/auth-server:1.0.0_dev - - name: auth-server-key-rotation - image: janssenproject/certmanager:1.0.0_dev - - name: client-api - image: janssenproject/client-api:1.0.0_dev - - name: configuration-manager - image: janssenproject/configuration-manager:1.0.0_dev - - name: config-api - image: janssenproject/config-api:1.0.0_dev - - name: fido2 - image: janssenproject/fido2:1.0.0_dev - - name: opendj - image: gluufederation/opendj:5.0.0_dev - - name: persistence - image: janssenproject/persistence-loader:1.0.0_dev - - name: scim - image: janssenproject/scim:1.0.0_dev - artifacthub.io/license: Apache-2.0 - artifacthub.io/prerelease: "true" - apiVersion: v2 - appVersion: 1.0.0-b9 - created: "2021-09-22T06:17:32.055669-04:00" - dependencies: - - condition: global.config.enabled - name: config - repository: "" - version: 1.0.0-b9 - - condition: global.config-api.enabled - name: config-api - repository: "" - version: 1.0.0-b9 - - condition: global.opendj.enabled - name: opendj - repository: "" - version: 1.0.0-b9 - - condition: global.auth-server.enabled - name: auth-server - repository: "" - version: 1.0.0-b9 - - condition: global.fido2.enabled - name: fido2 - repository: "" - version: 1.0.0-b9 - - condition: global.scim.enabled - name: scim - repository: "" - version: 1.0.0-b9 - - condition: global.nginx-ingress.enabled - name: nginx-ingress - repository: "" - version: 1.0.0-b9 - - condition: global.auth-server-key-rotation.enabled - name: auth-server-key-rotation - repository: "" - version: 1.0.0-b9 - - condition: global.client-api.enabled - name: client-api - repository: "" - version: 1.0.0-b9 - - condition: global.persistence.enabled - name: persistence - repository: "" - version: 1.0.0-b9 - - condition: global.istio.ingress - name: cn-istio-ingress - repository: "" - version: 1.0.0-b9 - description: Janssen Access and Identity Management - digest: 673fb3522dd7efc91973c108a472a38b1e6942c4fe05c47b23f7922a92a935ba - home: https://github.com/JanssenProject/jans-cloud-native - icon: https://avatars.githubusercontent.com/u/68292770?s=200&v=4 - kubeVersion: '>=v1.17.0-0' - maintainers: - - email: support@gluu.org - name: moabu - name: jans - sources: - - https://jans.io - - https://github.com/JanssenProject/jans-cloud-native - urls: - - jans-1.0.0-b9.tgz - version: 1.0.0-b9 - - annotations: - artifacthub.io/changes: | - - Janssen 1.0 under dev charts - artifacthub.io/containsSecurityUpdates: "true" - artifacthub.io/images: | - - name: auth-server - image: janssenproject/auth-server:1.0.0_dev - - name: auth-server-key-rotation - image: janssenproject/certmanager:1.0.0_dev - - name: client-api - image: janssenproject/client-api:1.0.0_dev - - name: configuration-manager - image: janssenproject/configuration-manager:1.0.0_dev - - name: config-api - image: janssenproject/config-api:1.0.0_dev - - name: fido2 - image: janssenproject/fido2:1.0.0_dev - - name: opendj - image: gluufederation/opendj:5.0.0_dev - - name: persistence - image: janssenproject/persistence-loader:1.0.0_dev - - name: scim - image: janssenproject/scim:1.0.0_dev - artifacthub.io/license: Apache-2.0 - artifacthub.io/prerelease: "true" - apiVersion: v2 - appVersion: 1.0.0-b8 - created: "2021-09-22T06:17:32.048784-04:00" - dependencies: - - condition: global.config.enabled - name: config - repository: "" - version: 1.0.0-b8 - - condition: global.config-api.enabled - name: config-api - repository: "" - version: 1.0.0-b8 - - condition: global.opendj.enabled - name: opendj - repository: "" - version: 1.0.0-b8 - - condition: global.auth-server.enabled - name: auth-server - repository: "" - version: 1.0.0-b8 - - condition: global.fido2.enabled - name: fido2 - repository: "" - version: 1.0.0-b8 - - condition: global.scim.enabled - name: scim - repository: "" - version: 1.0.0-b8 - - condition: global.nginx-ingress.enabled - name: nginx-ingress - repository: "" - version: 1.0.0-b8 - - condition: global.auth-server-key-rotation.enabled - name: auth-server-key-rotation - repository: "" - version: 1.0.0-b8 - - condition: global.client-api.enabled - name: client-api - repository: "" - version: 1.0.0-b8 - - condition: global.persistence.enabled - name: persistence - repository: "" - version: 1.0.0-b8 - - condition: global.istio.ingress - name: cn-istio-ingress - repository: "" - version: 1.0.0-b8 - description: Janssen Access and Identity Management - digest: 5a107366deb115bb5030959fbcc0fb7c02371c12f10fa0560dfe5b608d86d985 - home: https://github.com/JanssenProject/jans-cloud-native - icon: https://avatars.githubusercontent.com/u/68292770?s=200&v=4 - kubeVersion: '>=v1.17.0-0' - maintainers: - - email: support@gluu.org - name: moabu - name: jans - sources: - - https://jans.io - - https://github.com/JanssenProject/jans-cloud-native - urls: - - jans-1.0.0-b8.tgz - version: 1.0.0-b8 - - annotations: - artifacthub.io/changes: | - - Janssen 1.0 under dev charts - artifacthub.io/containsSecurityUpdates: "true" - artifacthub.io/images: | - - name: auth-server - image: janssenproject/auth-server:1.0.0_dev - - name: auth-server-key-rotation - image: janssenproject/certmanager:1.0.0_dev - - name: client-api - image: janssenproject/client-api:1.0.0_dev - - name: configuration-manager - image: janssenproject/configuration-manager:1.0.0_dev - - name: config-api - image: janssenproject/config-api:1.0.0_dev - - name: fido2 - image: janssenproject/fido2:1.0.0_dev - - name: opendj - image: gluufederation/opendj:5.0.0_dev - - name: persistence - image: janssenproject/persistence-loader:1.0.0_dev - - name: scim - image: janssenproject/scim:1.0.0_dev - artifacthub.io/license: Apache-2.0 - artifacthub.io/prerelease: "true" - apiVersion: v2 - appVersion: 1.0.0-b7 - created: "2021-09-22T06:17:32.041086-04:00" - dependencies: - - condition: global.config.enabled - name: config - repository: "" - version: 1.0.0-b7 - - condition: global.config-api.enabled - name: config-api - repository: "" - version: 1.0.0-b7 - - condition: global.opendj.enabled - name: opendj - repository: "" - version: 1.0.0-b7 - - condition: global.auth-server.enabled - name: auth-server - repository: "" - version: 1.0.0-b7 - - condition: global.fido2.enabled - name: fido2 - repository: "" - version: 1.0.0-b7 - - condition: global.scim.enabled - name: scim - repository: "" - version: 1.0.0-b7 - - condition: global.nginx-ingress.enabled - name: nginx-ingress - repository: "" - version: 1.0.0-b7 - - condition: global.auth-server-key-rotation.enabled - name: auth-server-key-rotation - repository: "" - version: 1.0.0-b7 - - condition: global.client-api.enabled - name: client-api - repository: "" - version: 1.0.0-b7 - - condition: global.persistence.enabled - name: persistence - repository: "" - version: 1.0.0-b7 - - condition: global.istio.ingress - name: cn-istio-ingress - repository: "" - version: 1.0.0-b7 - description: Janssen Access and Identity Management - digest: 2a6cfb429ea0e17bd895612758f78dcd6049d4af00f8d627a1bfca441abfad36 - home: https://github.com/JanssenProject/jans-cloud-native - icon: https://avatars.githubusercontent.com/u/68292770?s=200&v=4 - kubeVersion: '>=v1.17.0-0' - maintainers: - - email: support@gluu.org - name: moabu - name: jans - sources: - - https://jans.io - - https://github.com/JanssenProject/jans-cloud-native - urls: - - jans-1.0.0-b7.tgz - version: 1.0.0-b7 - - annotations: - artifacthub.io/changes: | - - Janssen 1.0 under dev charts - artifacthub.io/containsSecurityUpdates: "true" - artifacthub.io/images: | - - name: auth-server - image: janssenproject/auth-server:1.0.0_b6 - - name: auth-server-key-rotation - image: janssenproject/certmanager:1.0.0_b6 - - name: client-api - image: janssenproject/client-api:1.0.0_b6 - - name: configuration-manager - image: janssenproject/configuration-manager:1.0.0_b6 - - name: config-api - image: janssenproject/config-api:1.0.0_b6 - - name: fido2 - image: janssenproject/fido2:1.0.0_b6 - - name: opendj - image: gluufederation/opendj:5.0.0_dev - - name: persistence - image: janssenproject/persistence-loader:1.0.0_b6 - - name: scim - image: janssenproject/scim:1.0.0_b6 - artifacthub.io/license: Apache-2.0 - artifacthub.io/prerelease: "true" - apiVersion: v2 - appVersion: 1.0.0-b6 - created: "2021-09-22T06:17:32.033236-04:00" - dependencies: - - condition: global.config.enabled - name: config - repository: "" - version: 1.0.0-b6 - - condition: global.config-api.enabled - name: config-api - repository: "" - version: 1.0.0-b6 - - condition: global.opendj.enabled - name: opendj - repository: "" - version: 1.0.0-b6 - - condition: global.auth-server.enabled - name: auth-server - repository: "" - version: 1.0.0-b6 - - condition: global.fido2.enabled - name: fido2 - repository: "" - version: 1.0.0-b6 - - condition: global.scim.enabled - name: scim - repository: "" - version: 1.0.0-b6 - - condition: global.nginx-ingress.enabled - name: nginx-ingress - repository: "" - version: 1.0.0-b6 - - condition: global.auth-server-key-rotation.enabled - name: auth-server-key-rotation - repository: "" - version: 1.0.0-b6 - - condition: global.client-api.enabled - name: client-api - repository: "" - version: 1.0.0-b6 - - condition: global.persistence.enabled - name: persistence - repository: "" - version: 1.0.0-b6 - - condition: global.istio.ingress - name: cn-istio-ingress - repository: "" - version: 1.0.0-b6 - description: Janssen Authorization server - digest: 59fa68cceedd260d51753af4632a0efc904c98c18aa826a331143d0a12c7e9eb - home: https://github.com/JanssenProject/jans-cloud-native - icon: https://avatars.githubusercontent.com/u/68292770?s=200&v=4 - kubeVersion: '>=v1.17.0-0' - maintainers: - - email: mo@gluu.org - name: moabu - name: jans - sources: - - https://github.com/JanssenProject/jans-cloud-native/charts - urls: - - jans-1.0.0-b6.tgz - version: 1.0.0-b6 - - annotations: - artifacthub.io/changes: | - - Janssen 1.0 under dev charts - artifacthub.io/containsSecurityUpdates: "true" - artifacthub.io/images: | - - name: auth-server - image: janssenproject/auth-server:1.0.0_dev - - name: auth-server-key-rotation - image: janssenproject/certmanager:1.0.0_dev - - name: client-api - image: janssenproject/client-api:1.0.0_dev - - name: configuration-manager - image: janssenproject/configuration-manager:1.0.0_dev - - name: config-api - image: janssenproject/config-api:1.0.0_dev - - name: fido2 - image: janssenproject/fido2:1.0.0_dev - - name: opendj - image: gluufederation/opendj:5.0.0_dev - - name: persistence - image: janssenproject/persistence-loader:1.0.0_dev - - name: scim - image: janssenproject/scim:1.0.0_dev - artifacthub.io/license: Apache-2.0 - artifacthub.io/prerelease: "true" - apiVersion: v2 - appVersion: 1.0.0-b11 - created: "2021-09-22T06:17:32.026269-04:00" - dependencies: - - condition: global.config.enabled - name: config - repository: "" - version: 1.0.0-b11 - - condition: global.config-api.enabled - name: config-api - repository: "" - version: 1.0.0-b11 - - condition: global.opendj.enabled - name: opendj - repository: "" - version: 1.0.0-b11 - - condition: global.auth-server.enabled - name: auth-server - repository: "" - version: 1.0.0-b11 - - condition: global.fido2.enabled - name: fido2 - repository: "" - version: 1.0.0-b11 - - condition: global.scim.enabled - name: scim - repository: "" - version: 1.0.0-b11 - - condition: global.nginx-ingress.enabled - name: nginx-ingress - repository: "" - version: 1.0.0-b11 - - condition: global.auth-server-key-rotation.enabled - name: auth-server-key-rotation - repository: "" - version: 1.0.0-b11 - - condition: global.client-api.enabled - name: client-api - repository: "" - version: 1.0.0-b11 - - condition: global.persistence.enabled - name: persistence - repository: "" - version: 1.0.0-b11 - - condition: global.istio.ingress - name: cn-istio-ingress - repository: "" - version: 1.0.0-b11 - description: Janssen Access and Identity Management - digest: 0fec59df9bbcdc57fd9cb194a991f0fb58440c2484fa1807733d0adc89172ed7 - home: https://github.com/JanssenProject/jans-cloud-native - icon: https://avatars.githubusercontent.com/u/68292770?s=200&v=4 - kubeVersion: '>=v1.17.0-0' - maintainers: - - email: support@gluu.org - name: moabu - name: jans - sources: - - https://jans.io - - https://github.com/JanssenProject/jans-cloud-native - urls: - - jans-1.0.0-b11.tgz - version: 1.0.0-b11 - - annotations: - artifacthub.io/changes: | - - Janssen 1.0 under dev charts - artifacthub.io/containsSecurityUpdates: "true" - artifacthub.io/images: | - - name: auth-server - image: janssenproject/auth-server:1.0.0_dev - - name: auth-server-key-rotation - image: janssenproject/certmanager:1.0.0_dev - - name: client-api - image: janssenproject/client-api:1.0.0_dev - - name: configuration-manager - image: janssenproject/configuration-manager:1.0.0_dev - - name: config-api - image: janssenproject/config-api:1.0.0_dev - - name: fido2 - image: janssenproject/fido2:1.0.0_dev - - name: opendj - image: gluufederation/opendj:5.0.0_dev - - name: persistence - image: janssenproject/persistence-loader:1.0.0_dev - - name: scim - image: janssenproject/scim:1.0.0_dev - artifacthub.io/license: Apache-2.0 - artifacthub.io/prerelease: "true" - apiVersion: v2 - appVersion: 1.0.0-b10 - created: "2021-09-22T06:17:32.018336-04:00" - dependencies: - - condition: global.config.enabled - name: config - repository: "" - version: 1.0.0-b10 - - condition: global.config-api.enabled - name: config-api - repository: "" - version: 1.0.0-b10 - - condition: global.opendj.enabled - name: opendj - repository: "" - version: 1.0.0-b10 - - condition: global.auth-server.enabled - name: auth-server - repository: "" - version: 1.0.0-b10 - - condition: global.fido2.enabled - name: fido2 - repository: "" - version: 1.0.0-b10 - - condition: global.scim.enabled - name: scim - repository: "" - version: 1.0.0-b10 - - condition: global.nginx-ingress.enabled - name: nginx-ingress - repository: "" - version: 1.0.0-b10 - - condition: global.auth-server-key-rotation.enabled - name: auth-server-key-rotation - repository: "" - version: 1.0.0-b10 - - condition: global.client-api.enabled - name: client-api - repository: "" - version: 1.0.0-b10 - - condition: global.persistence.enabled - name: persistence - repository: "" - version: 1.0.0-b10 - - condition: global.istio.ingress - name: cn-istio-ingress - repository: "" - version: 1.0.0-b10 - description: Janssen Access and Identity Management - digest: f15e43d087950b68b1102dcf064ed658f6b65bd94bb5d8789dba46c3ad45c7a8 - home: https://github.com/JanssenProject/jans-cloud-native - icon: https://avatars.githubusercontent.com/u/68292770?s=200&v=4 - kubeVersion: '>=v1.17.0-0' - maintainers: - - email: support@gluu.org - name: moabu - name: jans - sources: - - https://jans.io - - https://github.com/JanssenProject/jans-cloud-native - urls: - - jans-1.0.0-b10.tgz - version: 1.0.0-b10 - - annotations: - artifacthub.io/license: Apache-2.0 - artifacthub.io/prerelease: "true" - apiVersion: v2 - appVersion: 1.0.0-a4 - created: "2021-09-22T06:17:32.008336-04:00" - dependencies: - - condition: global.config.enabled - name: config - repository: "" - version: 1.0.0-a4 - - condition: global.config-api.enabled - name: config-api - repository: "" - version: 1.0.0-a4 - - condition: global.opendj.enabled - name: opendj - repository: "" - version: 1.0.0-a4 - - condition: global.auth-server.enabled - name: auth-server - repository: "" - version: 1.0.0-a4 - - condition: global.fido2.enabled - name: fido2 - repository: "" - version: 1.0.0-a4 - - condition: global.scim.enabled - name: scim - repository: "" - version: 1.0.0-a4 - - condition: global.nginx-ingress.enabled - name: nginx - repository: "" - version: 1.0.0-a4 - - condition: global.auth-server-key-rotation.enabled - name: auth-server-key-rotation - repository: "" - version: 1.0.0-a4 - - condition: global.client-api.enabled - name: client-api - repository: "" - version: 1.0.0-a4 - - condition: global.persistence.enabled - name: persistence - repository: "" - version: 1.0.0-a4 - - condition: global.istio.ingress - name: cn-istio-ingress - repository: "" - version: 1.0.0-a4 - description: Janssen Authorization server - digest: b6190025bf248cdb9befbc027b6161905931020159fe756e7a2fcf470deff6b1 - home: https://github.com/JanssenProject/jans-cloud-native - icon: https://avatars.githubusercontent.com/u/68292770?s=200&v=4 - kubeVersion: '>=v1.17.0-0' - maintainers: - - email: mo@gluu.org - name: moabu - name: jans - sources: - - https://github.com/JanssenProject/jans-cloud-native/charts - urls: - - jans-1.0.0-a4.tgz - version: 1.0.0-a4 -generated: "2021-09-22T06:17:32.003449-04:00" diff --git a/charts/jans-1.0.0-a4.tgz b/charts/jans-1.0.0-a4.tgz deleted file mode 100644 index 1e47296c6a92afb105c7429b271da34b545affce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34872 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 diff --git a/charts/jans-1.0.0-b10.tgz b/charts/jans-1.0.0-b10.tgz deleted file mode 100644 index 19afe1a5dc48be2122ca4f0f01f147f331e3567c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 192583 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%0Ai*YF{3mNDQ~zh8x4 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 S{l5SJ0RR6pDCNKa0tx_DBJVT+ diff --git a/charts/jans-1.0.0-b11.tgz b/charts/jans-1.0.0-b11.tgz deleted file mode 100644 index 445215131cb83c016c9dfaf071afa058332057f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63966 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 z∾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

<59XGs5Q8Q8i#0 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 N|Nq_w`PTs81OT=HSV;f? diff --git a/charts/jans-1.0.0-b7.tgz b/charts/jans-1.0.0-b7.tgz deleted file mode 100644 index 2e3927826a2bf1032f8a45100e0e964e39e5f98b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63547 zcmYJ4V{~T0(zfGFFtP1SZ1V{wwr$(CjVHEk+nU(6F|qy4Ipp#0!b*<{Xs( zb=^f6^#cv$KjWJUgvLNzk7+S0(**h5*t zj!)dg+6Lrrw~yZjd&93yzghvvnx{=OU=8_1EU<}tq^k zv-8HTb1Uc5&Fy(@8grxb@}%=}>qO4E^D_H#>yknKUA8X$HKJ%>KkBLaJ%juT^|e3R zAq4$M+Law?KxmHmX^|7B>DSKPNLozz-@MG9!AO<`m}wFcSnIaU3Pau`@pAMDrAK1N z-qYy=Nq-1#NdQX^DyK%p{cK6$;znQK-2LfSSUK?s0|_`B(x(Mx!M>2OWk-2CG~a8z{hZT)XAGNB-%SL$u!ITc=}K@4jPa{2lk>u zaS%nBjWfe|4?VO3n3&W;?0*ZaMf^J&KfpB6yr+Z*KHS!3!~keNBjn)mwMryTEUuo3 zkrO3F)5I|L<1JUKaCW%FIdiSz(OLE5vjK} z-o**OMg2}}MCjOD>odUmhUsR5p5gE=@BpNVMf|m9eZ+cG24pJqL35g??^z;;LWACN z$p21ZM?xVxOr)kS!C#yOUOCnP4eK;vVFk6gG?BZo_qk^4?W=yUvkji;o2?|E^ML%x z0lzdIiWF@2pXzTiY$p+CFZkObu_lg0_0MCwCS)W1(HZ%|40Bs$m_=9WWSFHJr}hVb zv-@QK480(Yt6*_KS8}w!Xg1rPXmkE;!~?|7pWdyaZhUd#5sde9`EqFmmXWarerZT0 z>?7LG9IDhw+11s^AtvJoK)UzOa-H`F#nlHef>)VP^lmcT#f7alhFfw~^|6!yBngs- z8o4wu{ki4FTGyU!kFT!J><`n@t*>wT-nFf^a40g})32{fj?|6n%adx?#?R*%n36nZ zB+w}QbbTD>_~?OnxJLocRC5QW$ThY-cO;v%9kv48M6m%DxME>>0Xu~pPsNZ`4^uzu z7@KZ^yl=gIB9gmlc>j>MZ`b#=tNWA2x9=<0&gJjxgA=>5H4%o+1RT;&Li$s}I>tf* z+T~2~7#L4%6xX)6Cr+awrBme_sq@a%+StzaT=q;$Y4QRT&9jIYgmjWX!aK?xtA#mmm4=Yj|dTSzxtkuSIR!}_Oc~%Qt3eF>64n=KGPLLx5ynTeSXji*a zqUyPRAdHC_8GKoo3A9b7*bDb#+0Pj?4AO!pS-oyW*19Yn@&Qum0NA%hR`4nc5twrc zkuWL#g-ug!Vu_@)Rqs_m?`MwSg$L*Croa*Ck|ucu*CzJRA|3k>N= zxF`@5nXa(6hyt9IpVTz>H#1odYs`MOmK*XcjLMT62aB9u`Y7G|B3vQDy2rY$-27DR zpWrBqmLrIq^pVj;=3^fbV_D26(Q;YS^=TJiV*$iNB4c$*3=G2JDlY0Zn0uApIWfRg z{6X0)_+@Bic1x`^5OIFM*c}7;lwFHB^Ggla zS=2_dP!aj78GaRKA@`#FQMr3)L8QM6=cM*0ywSIkF}I)gE+-ZQYL^Qr+QZh7b#_&_ zczIF;BB)3bRsI2<Wavjh_{0heiZU&3HYJd?>^Rwv(x|E1_Cp zLepgU5T^O4#v)iRd8=b-RhY)qY*w>uIQQaXF(kq*N_H830WvH{u)3oqYcg~kn8uM= zjKoNt{s=GylduGVVPekW+~&`fkJm+V)Ye6EsvBZ(9lut8=N?`cH>J1<(fJLUILMAF0kxRK+7_YsQnnw;wj(-hjl#}is3=V~;6h~^0 zs44sR=2~h5x&DCC4SSIqVUi@4A=c`ACT#LVZ=u<=*pXkeTO~!PSxxyUxqw#>;o?rnpPW^u$hxrmYLEDgpi$+SAz{pDP~6YTbXnV^{ifNFu3l=bS>F1q6V-ogql;S6 zPJ4uo$tVj-XkoGw=;KmGY`As4$bO#3pJ3^A^5<&D@HWqKU8t5d4v= zl(70Un788JCn2V&z^@{g9AzXH93fsSrgaYVu|tQpnT1o44`+kf9^fsT0BKhiR9=Zz|C% zL-Tpe`>NhR4x%Q$?vuet9{V9nkR11Tl1He&B`j*SmDwb2xZ7HW-ani6$7`gARAB~2`#w$LC-IDj7c^4?M1eIK;#v z?nm<|ccX+&RD#r6-Gzt)!C+BgqC3K``^RM%4@M~H!ny*QZku@|7;#4t0@ELvMKG(Z zsAP^zqbNRSuDs;#NNL#!g{TEJZEc(`P!dqz*Hrh2gEatXMdDs-GV|wAQ*sB4o0&Nf z!xWMA&sVH4)7IVs0La3c%}x0h)|2iEQ&^%z7^TXWEJ77IkEdG+$wl;sFh4#*#4H$Kl^i`wCVW|rHPs&Jh6cMbNKq;M!3fi& zvX`@(7!(>dJbG}5$>Hh!8gxpDNZ<99cVCtM6)Ag^uZ1^%`XJU&u&njFv7aek9GNM9 z(z>vZSY24SYJ{RpI?XOEAz)o73|{6b2h%SNnsWfX%m+883nK zN8a~a>QmmQ%R{c;t(_?a99E5|dG;Rcw&%FFqm47se(NmNDHslRrd%F#ZpL!-JDCBd z8AQyk7hP@Ik?;Q7qW2K2%n#Gu$Mn5yRNLu5DjcH-S7seqkD@TaMr!yvG1HDn7#1fZ zX)&X)g(o~IM7B+!x#6c8{^B%hrty1gqXH5gOC7mYGnt)IZ0|BIBeQjp=WptPN!|i! z#oVH1LyHobO?<``9MV36+}N`9V7)>{vVnzFND(TM$pwEs3+Keqx|CB6M{evJkSPbE zxu3G%V&j#rns9St5amaLg&Fq6%&2%e*5Ao@WoTM);W`A=)jui(}hs=&NC(d&qGRytY&BoUVLc{2+WHN9Vpy!df zGi|=A67%H6|8Z_5%DZ-9+Dtb-i}<}RFz3kD4?7ekUyKO=PA*dDgEiW#e^kUJx|TN_3gc6L$qpcJ!s$9k+pd26V#( zK!aN@DOUD3&oc=2GR|Q}fW`L1H`}5wFg!t|Dqcj~K7saE7;`x$J+Kmwy!{I%!^Nby z6;O{hr+x)=(?7s_bE47M?wSh1l3Vjgiz+?oYYko}ai>Gau{Y&YF}hiJ2k$O_^@jJ{=R zpWcC{d%h@O)BkfbRAl_gCKdX}K-soPn-K1|Ifnif(NzB*{H(NO;?@=^Td`d=ua0+P zGp6|Pw-Cmx8Eaoo;-hC2=z1SZRU9r80?$Kjgvdh5OZLQ(>TmZPs&1-#JTh^qF;TW{ z1rp>_6D4VTBxxhHfDMY{yg}W0EE@Cy4an!VdzJUfmV5XQ_I+=(_|{;Z@trx)&Lr`8 z!3}m(@)fJIxLL9zhel%lzaG4X zD0wCBhR%Go;ud2UvGM>#2Afb}B?2`gol5sA*di%!<+$&#sJEitPI^gX-CE;2=D19p zSN8rrZ5)nGS`get9F)bU4)KLsXu8TMPaO+%FZR#A@>)RW-mEQx*MK*J+UDgoRx~(9 zo(le5SSQwsTh(X<&9dMEswVejwgaBKt-02&bi9FhxmVJ(G9~i9Yhwy!mVJPOZ?W&A zDraWfKiH3+BT0syqbWz8v)z7^sd{#1HgJA)1CVT2$SvSr+l>zrjmAWe@Qd-6E+mF!E&%8;V0 zNNo_z{ka#;yg@GDuQt%<*7*hPxL@afG5Duyj(+$z{n|egQOOyQ?$-Dbfu^fQ7>mSd(JfkmfRbE-fT$*)nOgXEg_>1meO-r(X%aEbM z!Ez0N{s0*PQFWW3{JwdwS0sT06ezRQB}JPedqP+@)0#*kb%O;0tS!mvWKqh(_irkn zov$}eS3CK11a)i&g?J{ZP~qwgx&xDp^brNrMwTU2rzfm1Vw1vHVUoQ*?Avk?z5z57 zGC=`Yf)Kg~ltr7L8@urbjKPQ}BTa7h^8&nFoZNQ#dQFP7Gjt2oy=UF&oFev`bk+{=BQZZwk`Q#8%?woLk+WT9u9$>sQE z@9#cmK|`>d&@Ki^ruX}Oo=VIE`?cvssK0Dh+`?l!SyTQZ*aJ$KyC0G@r!EHRlS8Jrks4g=#N9E?XD<`Q%<_s7f`Pv3gP z&FHQzGF7A#T1F-gJ<^Z8HYxERDo*n7q#0%Kww7HXy>cbV>-ND!ndc>?BKRX|m=8;3 zYtt^Z)uGQ)B4+9WCSQckkXlq%wUi`nJHW=Eggm3N;^-uyUy88|j=_{E_FIxtwWFhH z?!*cyik8DX(A3g(8Rr(n4>(EQQn~pSl_)UF7ikka9H1Y+Khukzx3?T3q>mZTLs?xi zMx`zhBS&f2BAg^HiN~jm*M{LM1lX#jG0?IF<8*WQL%SQxZXi4tFZo=#MjjoAl?gH5 z;0Q3sRo^P(;Cw7cLq;S+V8-NoW7O2li*Y~1lSr9|EH?oy{wlmLT5sQ}8&3}tDWNCp z!n>E1Pxm_RbveQS4>1oE%fx$t$sY~rm{5*t!D+o0`x(TrcZNCH${2#P9an41X%Q zFF-u})O#MPsRf?@51LPP1$>(Cwf0Yc+m=J+X+mEY+>)=3bn%^R>&%HbsFNE~IbfTY zqMaA)8|pHWED*d`zY+Juf^kY_0uQKf7}fJ{_X(}+qt8qUr<*_V%&4dJJsaXEtAqP> zx`E5&C7@X~3$Ws_?Q*Z?j0jK`MSP+R^&}`RaXn%Z0R2N>w=2g_la>^dX~R@Y1r^_;^$O{%qu_O?Ww@w7S z3eWiFj>Ccf{f;{M}HZ&9O5Tx@h??%ebGiUapD#7?l1Mr zXmJ;imMMsWE$n`c)$&);(|HyU!`*`$3Vuy{qk>hAJ0E-CxO*m?kK1l%jB+Fxj=pQYNwoHc2&9_W`PNcM zFzWjVj2!m)W5+2bp1eSWL49gakkt}PZdY4Jd@Y0w%{zI==~D_`H0^26P{1E)OHrC> z=&}B_5aYXuz`{6{i1~@dO9^#UNCDuPC0eiqY(|T%dS5r38sX#$-BoJ1v!%L*V+Y^j z&tEALmUzE07dhTNY5dID^Fv}%7y9NQshm%TFZkz|$;=Tx0vX2;^XLMmX}Mla5; zt{yLRce_nTANt-Sd%iWVJ~+jY@|49za6DonwvF2BN@Y6v%q$G+pdJ?^1pJBT#%PBM zOIzh;3hr3azob8fw4L3+SYXx=?TtNM99p)X@w-G>Xcidan|pjQr^42J?@#)8U1RwwipQT(z z+aXiSk?89eC1zg(5!c6`IAK_dF`nhhg+CV|*zP-6z<%xWk zh6q9u)+27w41WTJ8C;JV+smHhA;PmiM=AualCZPJTqG1Mmc|6d)qWvE)KK;XWK{x< z;>_G7fXh68fN#=KtXFLSkC=^Ulb(@mR2o{ku@IcrWO+HP_h{uT@~g%p(^bTT9F}WR zyjfy${yVNsHqZz|mh?@cz$A%~Afj&Aw#~AKv!z4U1?fxX;LvNIWdMJ=y<-fefuLqN zYZ+&#LXP^ux2EK2oEJ3Yau&Ld*>nuAX{veqg^rxHL{n z`{#K#VeThvJ41r*V3UkVx4ddx5E;%nF%H;Hkg{o(b$;qo=3mNq=O`Tmyb}?wx9v6v zkg2AC-~=jXgfBw5IjI6QS;WRy(npGV1XE`S4y26p2JK8odv)gKM^FV$MehO_|Dv{^ z!DSzN-80ks!QIBjyDcRHd$M7(lkxyBh%FEGRYPtkloV31 zjs`>G_OHt;5d@z7P$8bpf&j5_E{6Rz&1^og27Wol-o zS)(7>lM_FKn`m9-jm%1qzDl2WCuK~Rj&+We4tyY>E47>Q*0NqC@z*9lMfIIhJl<8- z$aOdN(p~s^B;)ib!#*mBr)$05q_hb2&tOd;)=JmK@kK(D*_D)|QkFAm$2oAgDtRr# z&+O*QZ_CLhEz?T*wTE2W*6LAqx3~9h9c5RS+!*>$6jNpPtm&D>?$WPER^R58G8%+* z$si1<(?(P(^hHp5Owv-4z*3)xLR_RvsXYwSBkRV{LPNpj^C#R%5o?4eC%et+y$7uJ zpFPIF`3uM*jOCifO&mYy$|&ys5aEzV%hc6v60DkUQb!TxCms zC!Y_$o*B5!C7H813i0(b^d!=aw?5nv8nU(Ii!<~&?~`*lr2gL>8x~*1#hCkkEF@ zmZeAlD?bc!8&X0+CT<(s;g^m+5vg@6v+v$W(~>+D%}r=G$Pz#_JsHs!fgKmtt6~Pc!-1#O=|DDd#P0sl&EO#J4VN5^`YkTl^9wC z#1K|NtQG?C1|AQv2h5%f;m|QE$*5K!qz*1@9~s&~{;n+7T;vc;_ovlk8{QkDlQh3& za&+(vEzynvW-bE0bl zma=<%HG-Qj%bew|;3XBzWRJ2hYBM1UmDIvZt-nB%bmljFQ3R*8Jgq$pv^FCU=W+>< z$FpDkrTUAbS)pS5e_6y4a0vH*j)Y-*ur-UD`Fu+_X4)WJWOaHrC%<4gn;~H+2UzQ( z0KyhZ0$|LDf3{!wFB0G}%3%7`8MwA;2Vq9Pj15{u8>8cC+qZ!Zt33eG$il*TILqH5 zih!LWLTs)d*(X1%-ki~MKz0!Z8^u%s||CaiGH-$ZQ2-220U=CrFD+1t^ zMoI&C%uP?N4JBbigvi(p^(wh?f)nH*Z_zN7IJz%EVL!9!S|e+kd|*s$9;3Mn3hO&a z-g?aizSg%9t4g__+awn3LnyKu1#L;jyNJLV!W5ADlCwdVQo5LQ;+{H7Q{`rf8pwO9 zIuJW-=Tj%7ct-X~VCRrc3xCA@@NC-Q;qTb=*z}|;V>U6_<-9E^OhXLQbjlV9D3Eaq z%aAf*0ibKutt{_uK~Phq$q<`^Q&(Brkg;oJt7}rG_)bR@dv0N&}=pm0E|b`@(tSjYQd;NW5QcITwSU5=RW_#Fx+6FnUy&*_=qPi3_)a-?rz%ik>h74u68VU{y}-mizxSCUohQh`Qz5DzOA=jME(|r zjS0gc!279?3GVsX8vcvHS}1;aZ@5iPda5;aaInbi>fh*qb`?9%``Rw|F;yK}uvYr1 zL)we>@&v5yp@-qMJ@=nDn*I0drHavERgr(;V@-AQZz0BWQyF{(r5^3&zSeERkbooR zV*|vmxfkR`jL%B-jAZXAGHi-kmuypauPI##J#}06-3UokfJ`f-yL*{-jJp)o8t;_^!^OKKhe7Z4VJJm zjiNF#rk|ADN=+8Yzf^d1P8%Q-wVwRj*_`;bMs>zg+N%h@z4&(PNu9lEX(er^iP+gR zYF6+5Ks5AIxU+OBykyFND|nHJt@-RlKJ%}F-Sy3jgLb8T>C(16U~;`(frsNu=)x3l zwBtxpV|l+Vl^J8BG6Q4jTzzPZmY=wDJfDe=Hk}uZM^+^oD_o17hlt0C0Bx6nk0Fzf zqe4N;+xnsMN$rwN6Dt`$f^)0rd~P)u9nQ+ODizc3&mE4Q_qjlPJbY%PwS=8C1=f80 z%DPzRmaV|XR*2umrR&oUXz;MpT<(TUct*|LxmeuiI8E; zORZ-%sZGke`_i18m~Up`om1AVOd&YH0xSe(A)VfC{`$JbbyMaBkJj<<4o>Dxb4F;< zoJATh**h&7FPV@632wH)CH1_x4doxD&jHF2XaJX$GtRN;T-Us9rh}oetO@6RLm9Pp zCA>qGkzQKA^%vSi6$irO(+pHigd<-=KoPdE>^+p z`Pkw`yxmFiCoYhy*+0_Xbc5UziFhUJ92(0M4E(El0@lYjJWh(9$oDhh@LWJ=B&?s3 zNEZwKg_bx(L$*RB{{uhGg_i~qnEB>mBb$})r!vCd?TSb zbBN8sh8xNQaXZ5qA=zcdB`V}9(>8iGUxlU|-3md>%*E>>ArdkjDh~YJy}?kGTld1X zt$NxuiY=PWevl=48`g{kN2&m*ounKNo$j}(Ta5x4nlbp5-I!ma0nO%p%T7nWd)e=@ z)CWP3GzsB~0x>T_Z)+U)Lr2^8xjno5Tcsg4dV%n`*&{nGiZF&+2qL5lAHq);p<+wU zT#8mYbkdyNa1W6H=W*CW(rfq@W=@ zYZpcXw0JT(%|bCDBlm6~-PnjN;s2cD@w#4%#d^ z^jC>kd)!JZ%BTBgugB6(C)eu54j$fvU5D9cW{~ao@k^ecljCeJ7vK2~-qRb$&hr~b z@5391*XP$%lse&p+erzlfIV z|KfE6zhs_Azowp{|IKZC?{Lz3U>uXvQ`@KKb^Om12VvC0BA>;o(JAS1sBkS*hqQ_70eQ*R&DmF548=s9Hg9W6P6|7}}19 z%Bn-(sM%${pT&z~#8g=ohFIfveEk?i@qtmA&*A(LT^%zAqoAg(on~*=tm0h{r87pm zDv(GMfnroc*1GB8V}>*eVoKABrhU?>t5Hr(T_eBsoZtpXYPYY2J<4w{h(`ooJj{XU zb9rC`iJBmB$mUX5<7G9xGn?eX>JTcWv7H+AGr_y)BPrQ3fxg;Yy~-Ym{j87(-U7^* zA--5$%T{XWqBp->_Fg@teJ4=Qc}uvVVm;pla6=(cs7NT1hQ|$Wi;uC(=$ywUk;Nwf z$=q@bWAEXx`?YbY)eJ1%YRgopJt?i0sIGMM4(bKL1grr^4Dm$a!UO@G$TA*eiu-Tp zNRQ!|pdWTNIyFW2UZ!u~{BJe)TCJ~yn;(T+A8&+Oz2EKcd$*}hh;IDe_fs-Yb3nu$ z-Y;9WegR|tcK!$ocIb+ESS$MHgF((r2t!I`VQE{cW-jzeZLx?cll?!XC2R-$5>Ux3 zfxmC~lGY8|bKCeI&5c4RM6^zwIe7+b0f9KYMznLXB@%}Q#(^|%)p+Zym<@(|OaLn%4=D(z3lTcskyfhx&8;0>z zX*;+v+Ouba{GKT#tzh3<`2`?M+zt;}3mGP&Le2TgeT^ z`UBS5YQNC?{Wcdqxa~frxAg$gTZM>6B>dNv}&Hbzuk=Gf~%f?6FHs{w$*Me)H+K+L!tj%SyqGD;CUx&rp zO)jv6hrXoLBn$xaB|uOd#36JF0d?G?_c|dpb#hB8bsJrb>BNv`VHIs+iS8=}?~M*! z7 z)yIaizd(1+3@pT)G9Y)UV@|p0D$yYA zdmnx3hZeZ}?Qx=wR!M@`OQCb(omp5=^t#47bF06_aCL;E@AP7MvWae$eBmEnoelgm za!WX_?JS}yq35uZCl2x4NIY}UNsr57m)DSRO3q?{kv|OVe{`S(>La}11oukf$V8^r zhi@wIPw^J0C^z$pkaqANe7h<&aF`Grdg<-Yd-hrpLgPi-f_iriV&m;XnAUkDs5jdvQnP>S3S+<(6VbG@O#d50O8<451U?8 z<|y8oywy@;+gZ-1%l--M&gi4Xwr}hU(Mm6(2P_yo1iCWUslwqsH^)Wz_t%5X1Z_-nS+m zH%rL~MI(*rgg-(Igk_0KIw|h%@AHjAc&1rfxeASClg!m&-|n*b`g)mA!9WN~RANy( z4hFxu)kxeIXA8x0kf|;0P1}b&N;l+>iXxcQzDQWqlfme)hSKy$Pbg6??bIH<6yj_f zQ2Hk+RLXqzFU{@S{MZreZO+vD_BHxd`K#cbv>>89Mc6=dG=1VY4<;JiMlG9@JAy_RnSIHB1z2rfH0;M!}0avJr zoXJ1%f_jcU-0Fhf*ZGXBX@nj~ltJY*`f-j|Y97jBTAV&w* zDS*ETy&mj3&Kpmakw6EgJDpJx4jDIeUGV_G^WQXEyM_a)mORtuuir(Tl*sW$JmKV~uZp|Y0 zjUBPOF)i>|Xm_1rQ;Cs00kg-r`4)~j*`m8K$uxW1 zv*EdU>;X^}cNWaJQkWvJkg$1_(e9N0-T{5YH#&3(9Hs=>EH1ao$!(lzKs=T*7mQQPfs^^G!Ey$L_LO<@u(e+ zY)Y@NS+u`%_NLGf7(YAy+am|J*e2&pMFseXAf)Zz3J;ooe&VLEGloY=pHnTx{cA18 z<~U@o;F(KHT|2U^?1DBUN)0^BTJmOns+JP+A0p6@Hz_k1M^Y>NrNs7n70z7}RX*+q zeCG5EaZ|}AY}jopn9EH+-Q?4-Ld&t#s!&!>Ud{F#M~i54YHl&tT2Fg-Y0=+rpicFA zQrwR(iRAI5BGtTI)g^5do#a40fMaP(>9BZ?2+U|Wotsz~-EIF>K@&yb;uUwD=UK$K zr1?ik_q6&s8Xhv?fJJL#X*mlf!p!P-#`%)GUMNfTFI-Aylw+PD?ERJ9cd4ZgSLm@@ z<@+Vd{MnnuXyjH5$vw$qQROfDwr`ZnruEZ=FPjqw%Lh}dqc$(87&|_GVj=p7K2oiH zH^cM14v}^=2+r{V4M7d+)L3TLUCU?+Z$WUw!Tmd-TSlcvgHLZJ+Yr%cE}06Rz{5<2 zK#Qw0%r+Z>0un1K2pssUp^V07kksaYj+}y2`z8D5Q!lsf2b(UbCDpA0&*h7KC=DxX zTAy_AAA#l97mf(w?T3JL@r3>3<*CQFbDTfu2jw&*h_$Ajvlx z@wW@w8Y+`oQifRpPk`#;SEz?K%v!Y9=Wb~a&^SlY_hpoVaAoMcJXvZoER#0Tuq#JS zpl1m*yL3%|Btsck@v7=d5ZYYRzDMq)bhdH>W(0#C8-}9wmv}bD;<0~f$KhtyvBSv} z25nKh@-!+V(5Vsca}nCyzZ-6#4-5l23Kb1>xNme%M%!n1r09Bc?73g&Rw6+1Vt*bX zr6%p~(eAG1<771wLk;~QO+u&pbSZ2Bs41prI^_zx!R!{@?bW$Zf%t4ujbH7GXz#B7 z8~G<5y2CWXkt+U)t(`L zL#x@#$;@?#7>4SxcN(6RJf0}+?siD$`S1$e*Z$-+6k2L{2%V{WYZ!>EBMw9%+gOg7F^fL-!uYu zy}4(-iEKiApzX5X5X;MoZ{_v^#oZl$gnjUL8M`~Y+d^1ZkVP2J0 z-7qY>7wsMS48B~tLHsy{KbW$00sL-fb27}GkIv>H8$2zF+6@Lnqf39YHHPG5VS7FZ z6sSmly4H4Ua@=#okGWmMh(Upj;1fC#|VaF93b{_-ukl5BB}q5>zc*U%k#Nu>R?XZR@U1;418 z9;h3rwrkRspM2wJbl>v^6o-}4gSNZ|pVqnydy`vik?7Y&K=hecKFF^)?EKwCkSD9{lC zo9+%`L5eO)DcJHcbm6S(E7VWyVprRfa%i;(6U9Mlp6L-tMZ%z=`pi+qAYbVk@%=-dAArM53+^&;4=`K#afN0?6CE&tX z*p@^IlS&VX=GiNQ);+WBTqcn3erRv;*6bAKlf?~~#3k?mB3Q9VIaW}6!;_Q*a%%J= zM7!T5jSta`-~~T8_Y4eb^{DHOlXoTP&G7sxjpv5syX*?bHb`H-v}WWDfqytk@S{>7uE zC7&94SG~17t*Y5;9!qkL`dpT-Zl>kexxXLSprACfVp`E*dy9`zDOmyKYie1sJ#8(n z@~b;$O}?L~j3U%?gT}1EZ2FKOrvnUN&M0avvc{$@g23W@E9>D-y`ak%f(s^T_0`BkK=x*a1?!*+tBB|hW%}s#_bHC$5V%>@7C>gQYq}_p{QUYX~=~a3=MS}l-HR_fYYQ&U@5zk z(RmFlEbv}QT~p^oHa2q5rjG*mdzX7i`nZ z=t4S?RiNQkY8kV&sVU#wUz(aL`dm?^t|^D~kD(db_33dqJAIvdjeC#(9(^^*l6;o8 z@rxQr=zDc3sx{D>RAYA$HndQ*o(8=1yoY&asz{&3EuIY%X9 z#0@ap4UZi;k39V@_A0>$A+xqFY{kA6bQio4bU%2F|^qiTs`=w z9d5wyt^2BM?LaH2>5S3wepRro; z<5@1bp+H8mQ({fCu2pWzByzATn+KrsuKo`WMc^f^^8E7Zs}_%5BD4Bg)q zZi~qob;;GbCNe+aB#L2HDP2iSIzc zaD0t2Zc#A!sl*zcW_EtQ4YSTd5Ww4W4?3i86I)ttVB=n4U7kRUZ{mn*9g@^^X^S$v z9NWV(ci;ANkD|{?&Ymihy?lQ*(w}Y$(dvWLMnE9;+J4odc_rlRcXUn5f-VAX_RinR zhw-EfI=S3xxAOs7aB5JA6-;&ff-v%);g#F;4XwU#)LdK8K@*5-1TbcC)o&$%INc6 z?BA}O#PT%l#P*uzYH&Vi7GSCeX4Le6L^@-y7@9VE$r=KBUolETDGA_%Q&SR)ak2}V zbX0vPL&)a9(J`jEeyM@#7ot-}krZ&}Uqe6+|JzO`huz_Un0;aON6r25)G}_{($iFC zCHnxtU`_s5Sx}eYl8&iP3c~eBIU!Gpg3eN)lngX z`D?4Fcr<;nk~b?}Yq5>!hddK+od}s?ruqmN0kD1SC=sABz7YDaC34Lpy@_!J8qFiW zdZD82#z>Kqx3vFSG$65fo*Cg*i0TiG_C@6U*AV2Va3y{})pKXU{`?>y;M}r58J1`} z*_rYr!U%J_M zPY79NWf1)#0iBvRA$e`hvlRSyXSoZZai!PbLxk2$4+tlp;;GW~Z^#b3&?$<-s1=U{ zcWvjy*DpaTxb8&51bW(CrLa-Dz;s5>G_PupcFi z(VK^Qo~^-e-390Hyw1kyscy4ayL{bd6lmf%@&rt0Nv9SmYoTTSly6D(P^z zrjVs@Ayl@qW8}yjVS}hOvXBYENGe9l*{9;ipHx$8Wvm<}ab+if7D`x7aV@!Fmj^LUOI z+7T(*W6Isv|0?rO6SbQX>ou+x3wTuIN87&2XY@HQX186fc~V=`Kvuoi&{BvYZqB;H z^_-VdcgSD2zGo@*hq`4&XF(}Dj;XX$$oCo&H>11#pJO*-&U}f2iND1n!mTH}=(>0h zKs%CCb*>a(>JFqYjcSH=6^&Fobu3#a{?$9S=$kjlF}*Kc%l|VwB5l*5A5$in+uLvF zZcG%h{5u_Je8sj!d1MZ41w zgO9$a;LG(@-L_Y=1NEfq+voBse^R1&qdgGz;y%@dW(X3na`=9`N|5rh%Ww@kZ}f4y z02ign`7M;wwsIEc^0tTnq*Y2tUs5S6z#4b)Fj%3y-(41_y|u9=O(T84 zSf6-vNzEF)n8BT*5_(Et5k-wY-;{s8uptGPQr8@XK7SE}Rm3MOI{+_>MV#cTIRIBF zuQ&jA@y?4yBqLn84dCXN|BshH%7+y?wF1*6ayd_CYC_aA)dgd0EqY9LRG8{6|7)7|((uZ_MEZBq_LmmA4=)J3Q%{a0bHhLSO%{FPz4J|+a7sm#7 zBrd8YHO@s+BP$@Jz6CfwZCoBlbMmFK+(D?Ad(DeBK@eYF?fHB`SN#UL`9U^ASxZ%4 z<*K|G8~K{jTxi%6wXRTKbkf^VSb)&9GM%)ZuE?W=l4NN%enQ&c%tG$_(U(P~zg9f~ zybFR-^-^&BroIsM2>WmMLj^%34S9drQ)!!?-I<)m$ozV_B9Dq?sbq)ArE>apfolcr zS-We+qHOa2Ekp{JyHlc;GH*Vt@&vADWJL9}=nF(cXWRsuNj2VlpMMTby`I8BH z$qY5G;0{*LLz~FHcm}CY55!}dKeaHvi$2SDBi$oke0%}wk2BL_;gZdDWmw*4?{)oq zc@tP_^79rFbY$H~ajC4>PBw_N4y_RFDjSn>H5{vtdY+Kuz?q89%vS{DYrR2LyGLhB=WH zO9pM9IC6IK)T+FME}js|y%3e}$Ckf`M}2mcG?BP$iN_G@1c!5#FJ^n=6__rzqg!M? z#Ce7{YU3!8{ZDz7qFKYe&DC8`;K`FHwLmL*)@?<>qZUykY0nWFXb%Qe&ZK%82?dIS z6zr4x^1~}o>i<#o55SQ`?fX9*+fFvNlZ|cL_QuY}*x0sh+cq|~Hnxq4=AY+zes9(H zt(rRBJylaR-E&T#tM9q4&xvqEA+~ugNUnlYhKf)XWu(;l4nq5RiIpEiu=0@>iUY7x zzW;F!+GYbQBsb1Y53R(7yY92{p`Py8Oq*vj7dfjBsKDyN;IAAnHti=~UFP(;X)1l- z*!p{-HyBGR-|d>TRVZlZsE+wJmqwN6I6!e7=5y&L?|IGu+bSaPZzHy;j=*@&7icw> zk`76!ny}nSJ*}kuy|!m(l7l_Nz};-)ZKRz#%jmB{@}ow{+O+jf+d)o|08hR9@g;>HMb@#16slMqBE;fj_IPvt^Ep(rA18 z8~XjyO=#edu14lx3L`w$UHNXh%8~i$&j59QPL01M@W#FBWoJAf$|{ zKjl9vh9x4^h)_^MlC2S9Vl+tnptPGnIf}@1MF<|wQByj^-4BCbwPFZeUnSRGtQEccASBqP)?v4Sa`GM^-w)dLg@j0f zG>NLm9YO&mc6$w}UHR9^C=HR5+6c;0;f}w=OyPS>AwtBsR0#biJ0PO2nW*^5MN}$a zx`0DpbR`Iz+<=HOc=!=)Ax~p;mN&t#pZKGHe*YDR{Y4k0*g>QI%q7I&1kGqTWvGYDkO+L;0r=UWi#PM zy_Beb{0DXdi=6&|y%w?yBuJ;8)q@XzV5QWKVGZj-r=DR>QLmn?59<1Guk(w-VvB)7=~%d)JWIREdJAtpHscIHwR(X zzLc$1OFmmt(fV52x5#oMjC0HJ64)$N;Gg}hP@QdxKVNzXaapc7Id;nNGDxZ9dFoc+ zuTz~(in~~P`0oJ_LS@z_&E+bQ!|s0@>Se~WS;@ffgAb34+3VaEb>Az!u8YnI<^U~2 zi}ZI*Kzrc-%bXF(A!cDH*uXI^rP!vjwjj|aFSlRh2L`A?W1i=ORR+O1$6e%$s8^on zix8-l2fYes6T2^)bOWd;j}Zcbcn`G3d{{6o^&fkk`8)c-lvk zu;aHuIM^wk`uKOv^?s4c=oQfjy5yQm^)SqZbSA>&5QCsz&z3Za(3u{d+@&76ajjqA}D2V$1ryvHg_zn7?AsH?Gh-=Z?HF};i z*1o3(RvL5joIq_2nkrRh#9|)ECWQgHMfEbH%rRFubM;E=Jo~G~nm?TrV=anp&u1$F zTZ)5ax@ATyz9$t$<}=j|Cx<;xW8v373LC2stcK(%PRuHRhqO8KL%JF+kQU* zpSz#F-#525jErDV4Ke#3f+S*ckybUF>Y6yWt}WlzE_h(;=GD5N`^J@pq3um;-c74} zp5Xk|cYA&58?a$BWHg{ij)`U1?69>;@02$_o9Ac$CxY>;IEY-?QVP7#Vuurb8p}bB z=XA0LnTW%sDkkUxg_Y=-M|RrVAv(m`Zp}@ef0l-<{Co7+uLw;ah8Tb-*&z4&w-i0W zBaxC898JoSLOj8C3d@L1iO%Ns*SRc2g^GH`;7vo|7z=#ZD{Z1f&;>gF8)JqGr12u! z^GBk&Lq!r5>TJ2o#5tWaI1^w4MMDe^YpI|B1n1`!fucrJlOk4#%77V?(uXULg*))6 zsTE2F(bO9Qi_11uIKW(iPBsUQ;SfJ3i9znTRMT0kxXg2v&918mQS43tUOB`5-6I@v zRd24TO)faB3q8>pXB!wC)FW3yl<{Bi%r`!&zCx-VqjJnjIu@a##|Jp8Q;UCLLIYXY z#bSU|SwKY&^ZX3os&K9O8{B!>n2KETG%=~a8H9>Bnsnr)>i*E=<3zfabJWG;)0PkK zOsz#fm_r&Ql@5pympnGh#rDSxLeBHzp1|LM>%#89*L?Q7o6@+Dci{8&gT-v#)Fp6J zadYv!9YF<;7gl5j>Z)gpjbrn#}6jvq(tVW zCq7aN`oCcU#*vjYJfjG4D&kw>qw0;$^h{`CZGIl#Q6aT4y;Zb4nd zY9q>kPp_td89S#RD==l&Q|n2i=D*;>_EPt{{^6nHmp5=yZi~}pbNH^FvQ$zdJxlwS z@$WkwPfDCfdpDt?33udq7M}*+N{!6s`pzXANV@ET&W!SH?=WPWCY#ga5{mzYsLwBLL2&HKXRNJx(aZU>-$8y zM>*;K#UZ}-Qt{I$VN3ebA|I65Bzx*RDGp}XeWTPQpw?)+pj)0YuN5gY{<)s((5JoS z>t`J!%13taViRYY(Y325N_fl=W`hK7q1xo!=Wcb@G}I&0`KAZ`(zvO)xw}}ZyNvjy z@cgJTs+FBE9B0Wn6ICLLQSt`1|0tWS!gGqU0=myc(poZ+Rp0?B!(Ix#Ivj=ZQzGG$ zImQB>!QC0~Uo6aaEyj#)kUw%Or4}tITzonitIh8nKz?d#?+G;fX!GClZC0jfugvm} z&HBzGD(7P#1BD3vq^5#kQe6>u_FHVax}!(+s5O<4mgLw>@hYhp`l_^J_wj+(79< zZ+mp}Hox0p*rtx$sGa`f@qf@Q&L(n!D0?X7plxxAjMDw0{51Udrzvyojhs1SnedK^ zn-P(|N2r;*G@biXRABnw>=r;d?p=7YmYnXXQUPo)F`9A+OlmUB9A30m>3g$I{eR#s z;IotusNdQ@(`Yf?2osi+a~@Ou7aJ=4I8lvraj` zOFS4rQdV#9Un?(VZVYVilJ~CC$X^eZB#matCJ+r~NmUR51{=0053^q`#}6%=g*313 z7LhZqDl5y`^t&Yp^Buk`Mq#B!8f>CBem7cq!a8D4Xx_~{6G!xmxXG0pu`hkvwG)xq zMO}?KX@Jh`jJDM2uJQ57p|ZJA);VX}ZgHji%knKqECa@B6L6*|)lqz1gbEA;@!RXM z&a9hT`{=H0-|OL4*{26oxvq}wn}xUFckxs!K~4&1Kt)J`V*%bXv{t~B|Cy5k zy7NC3@n!Bpo@ZZ#CqH$FH6Mtv(0BV8L_d-*LJrQZlX5;@G@{{Q&*d|qE9}yAN<0?q zEex|VidrnBV%_3+4O{xGe@KZ{oMp@#`jW0u6-ok$V`elr4Z$TO1&|O?lIl0teV-3; zeLSP>Q5m+O-RK-V86wHx3dMEZY9&ur@+h}r@x2!is3|2hIFlyuQuq?is zNP2F1RnAiwC(1r+t$Xv9Uevlxym=reRG9J~o_rD3So6LC_p3J=gMrean+tjuwqZe4 zHR&At(_g&0rnkRMi7xl#IGc+qMP;%m{ zkK3XVnr62^zDcyQ1&^AO z1!n-&-vQS-!#v0HI?*Yi%nTtbCq!rUu>gB1RU}{JrdOQi{kv|`hMkE}BF_2cbgy4x z6oUvz%WO68rNlNw{9c}KIHjh_#{&OJyyxabS4p6-fM`SzlY!lsQGe-P5zzQ+b zx$Jc6xU3VbTghKxHG%e{*dG=5X$S<`oime(#C@HQC&`PT(@@~Y@PMDRSh4~Cz#sV(U722 zy#7d;ovecc2}DeZ-L61WM?csWlN5tMrTpRm){e-o)lX&_S9-U+%Xp-4-RgTX7x?3MJS8wFO}68@D7^LFx_VI#txaN{Sv|@O$VS z4srlr+TtZe$cy8)5%0VLztR=09v_%tq=fa(WRXCqbbJ9Q|DhW^^&D9H7+p+)_RX67 znOa|A3o0uFSH#CsFJ{P77L3LR6U_?t|2RuI?1GKM^e}p6uwV`ar4zkN=;(Ze9R_HB z<3S@4)O6ECP0Oao@skMgWT;iwRB!7u;FnSs1KEYDJnKaqvE;tH0kf(7Lc^o$AADL; z1-?4tH= zBSw+TOZO5gdllNVb+?a59d}M^+2|kH+Qd`l^LaEnX|%VgK9=ppM;24yJ6AO{sNjYM zKSj6;Ry>QCB&gUBUr|Pkh1vWSx0Z;Kh?C>RBgkPJ!~snS)xy;Bs{STP!uelBj>nC2Mbtuidm8zWJ1nJnwn@ z?fjeb#*PXWui>_8!Tj+q`dHj2re~XrX z$rA7$G{d|yB0#>{PNSVeo)c$QhjTOheNBG09%#d86cRqIRUS_Wz%cUpW%yfo)e4FX!+lz3N*`wgMENRzCc(nfGBe8$7yT_A6wC zndD+xeIv^W{$*m6QuL$>&w*_seF7!Z0BRyJQU^KIiR9ezH<77i`J2cjQSbpHq=TLP z5NxAtC;;Ic!5CHD18I^u*nv-F@>4!3pJX~7_1ndN1h5}zVJV}PvQu!I^E%?(vr#9> zm9*=a6T0h)R%xZLqoL!kn~8;cnsm8_#Cg4(qW(SkdpYN=xD&rXxY|5nCe^QqD*9)LsKmj|^VQLlpL}a*_5h zwZK6^?>kA_jT7ba%KiN} zHLf<1%L>KPES3p0r~H939$25nlpeyN4*p*UdKKo&ce!`!S+^PXRHMKO(;GZ>1Nf)P zXOyy@*U&sJ>)X%0I0jc#u#3K{1==0=9V9It&gQBYU1JlkW%womv89v%dQl2G+AG zptp)fhR;eqAL=S7E8&IJHWI`irUmJn_xsjPE9mn-${_}UEEI*ev?ubh@Y5@QgywsjJ}&@lS8LXm|ElqS$6UHX`9@MWJaJsaa3%C+|b2Jhh3( zPWk<>n#5<_VS|^%H^=Md6;B_A=!0x_%mJP<71-)_p@it>5T%f7OqLl7#UOPl0YmqF zPr5(n^n$Nel;7-7)@wAL;6)-^p!ld=dZ#wUVhaCA>eZ2CTT~wg*_58pL^bIVG>J2z zeJVmW+&V628545;%sV}0N>wK|Xt~bNAEX&t%+06(wYph*#{rm%Z_#TytJ#@^0yDgB(Rb;UMV=@Zx)YvluZXK z>urwJlGbFx^bwWxYN%!jdaYSp;tZCYfs$TFJ`KI}_22_ke0)bpH>zq&?n9{{{&k-i zZw-f6vEQa=HV-e{m(pPJBvKk2D-ryTTnksI+<$D}-XV*_YWu)L(iE`C^yzs1RSNaEmBTDQ z#8h#fwXP=9q9|$@WbV*k^E>lV0Q)F%OnUm5$2p!YY=Ol>sw!Ul9HnC5m+{_2SpAM# z4R44430<~=D^Y4w%1ZqX=DG}PI7X(7xH}AnPo704eJ7t0mRv)DnvabTNwmuNq5p50 zk7}>@kmPDRs)_>w2|W|me)A+l6f{yEA_w-*ty%3=Im2&I1N-JkPPQYvz7GUA?baQ^ zh9V`%Pu)@$oYM?@rq>pV=I<)s!V8M)SFoVGc0#p6R*w}&k+fNWDdSyW<6FjgO6Oh5 zB@N^4c*;|ePglcwzn@s(8g^@CZWV!f%SM}|MNdYkpz1Lv4CmT#ADqPxG6>6R4Td8P*YF0Gru;epc{wxtTZd zXmL)(^bW>a;rkwHk*WvUc2JVK>8VR?ZP8`-;KFeINMiprlFS zoU8!EPHbb0x4nNsAEgo61nh_dhH}^nn$8ca;H;t!MO0i7tO2lzDNT>rT-`pgJO*p} z!PF!R%vcxrJI|>V9OneXRPhtbaDFdftMJElH1<2Qt!X~kqdT5IWfTTMYEqdgBcm({ zH*0-yHR#yw%a2^(_$3M1P74m}T1GG6PniOm>VMTr89AJ`3SOC#oH@5*jw$TxufvM% zzAoIHj+Nh@bcw^qw2q2-MG8*q^aT_)THrS zkpRS4JsTVZ$e%VSxN-lYRRU_4Mhm4{$!aKTXDWcKeVJ=6o+?3>rmC{1A%7r+@!wEf zX#~xfHZh7qt18pkv6|W_2~5q>i6A-2NuDYH-O4>SNO@DOks@ zQGd9j5fyB@pJ92a9g?loJ18HEg>wGd4V$4r#yj4}cc1ThX=u|f3&%8^XQNTKzK&E+ ziRAs^ph_kiZW8Fn|%Syl=x1ercL^&%38e%EjOz%3xDDJ8~4mS8FW8t z%#ZQQRuTIX>0SmGx-G30tMHD8Z!7SgJ213}zg;CeykK2TV!+4XrDeP=;&w|3OQrWs z;X^UEqMw>G<~ab7_<}XkS0?U2zmiO{@RNS!T;V(ZroRR*T;*MV=}%{!*U=OSb-0Py zjWy{RS@V8h9$iiY$feY5DNwGL)g|3`x+fAqqyWG$L4iklnIXrdC7Kk%0x$DhM|>@5 z!OUzl+~r7H1xP@dQ>Jff_D?Tp6PR``Vp8ibl*VsQ-(R6j-;{u2Evf&ri09vXkZ#MU z=mL{08BiwkQ2V~!0@azrXW1n=3U`cy))s|pQXRMcwQ`auNH)QUAsRNI<63p3V}hEO z!s{E2ISy*xtuhCK9;m7CKN6U9`OZmJ`Vg7t{nOiI-;gv}YaEolU;O*rlL4*28!Ee5 z#8E{Xj(^sKoj?5iCn3&>%oJ5F;FiFW7hb;|Y(+->slwwNPDhDlpNa|o`sR{0wX44> zbMY+*A$2MuUirSP3^gB#WBrw7fDBF_;#Ok}+wxgp_)x|_{1mb~JUWkQt-A=xWAg8~ zcsNsRP9T4yW{IX>StkC1+}(@?I38D?7AkjyJLJzGr_1~~EFvPSs&R@`3HWo2z;ft7 zV(A2h^(u0`eIzBi-&UsROx1mE;=I&6X2BrD%iL~LDCd!ya5Z&eu-bLK9Xb6r-Z_ls zy}qcVir#GKbt9raH3tL)?MA{g>Ks4X*HW$k?P&6Szq~*lZlX(8=hzO_z z<@O?c0%D{p%ZVGlU8-`VA=oB+AIi5U%{+P!k_k3zp1cuy{Y$95k z^#=io`}S|LcPCObBSnRlRheVfA9Y~ArAgzYq7jUoBDk|kJPIErV|ye*=@1{T8PckP ztGPFfAdK+C8hmgr2ebp`sPy6tb`~;8YBFVc*1hor;m5h@dn=EA_{#bs%TTl(yb$NX z-y^`>jAU$0Iib|MbGN!SxwoY?QaC;jY2#B4zr;t`pItOk&Wd}$2GQdX^cB$#N94=d zc`pgoih&gVlOQ7n$`Ez&YtqO~{63IO5>U9H>+dVyK_k)xlyv@1_KL|u*ZYf>Jbyf0 z2YqbKyvSkK(!7kcNL0JG^c0V7FyN5`v^s`#HnTpZ^a`>d#E^jiEDwyd#Jc@F#irzJ zZ48NCPjQk#d8lVg?J>B(8#a=`VKn3dH@wYvf)@9Y@qno@;sY&3Mj!ScsHR5Je+V7g zXI*nwe_>h!ByE@WOfgosgWdB*m0iG%Zrj=^TcaC=BK#SIpc;lWgs}P*YMsrFCXeM3 z@GGiD=;^$KoXQ^r-eox?$s~W*OA7tl>V`uBFAxa$N!)bBe!)w^4Qeh}wURXv%X_6A22vB7>E8$8NCjDe@FsgH_!xNH^`m8zuRGoj@6{_ElwUOh-j zJG7vlUUp7U(yu|>@L&l8lt|r@TpxK0bf&zGpe$a6td&sMrCNDs3Cxm(70Kv!IBL{JiZUGrDbEvrj;2wexx9sx#8 zk~j{+GiMkJOa8q|%oX<;$ulTCilpLuC*}<0)`Zn2JhbO{yzzVw;=>B@y4= zfunv3o9(N~4{m}83)N}>lJ<1GXVVP}bl(8=ULM3BdjVch$7R~4%Gitn@9|Rlm zx0wxLE5+!w?zumXI>g%t24Y*C{lc?2kDg|1NevS43^|G8I|cCwIH(I}X4VA=I1ZW} zdb{m1zqe>I#m?4-8g-i7{<|EAp+0ErY+v2B;qBEE-%TNy(tO4bEMuB6?CZqiyZFHO zmIjl?$R@g?d-s3g%APig1~Cp_S)fE?%M!P)A1yR%0MUXj#+K#PLjOTfxMLbgTiUoi z1<6a-9e~V1_fel+-z35LJct!}{SPr~zL#Inwjz*J(zd5vc@kZ>Fwc2phCF4XckP3w zbQt%2Tbc`zuZjC)QZE==8m&OOjrS#S#CpSN(Co$AujP#~3z^e}(b#rA;Y{JvaigTV zC_gXcU~Pb`4B;3mNDAU=6U9`Bcxu zS%{!^+4fRA`LmFmeHqp*b1drOYBMn-Ebi^lMCXlB7;{IT_%-VIHt}uz3ika2ibACY zQg6UEdf8rNhkLvaZj4HDfH&1LdjS3#C+j-M5HW)`Yv;55;Swn^cnkYTLrYXXLL~L+ zF1VKSqY)Ih2!(J<)E+`)#ga{Y?3ynM)Zt%yXcM7|0}W;M5X|^5JdYx(vZ!r!9$x0Sn3jO!Io<_0Gzx3PYSdi=-aZ5W43;w8p;I>Xmv$f zbmk>EnQ2sI@xXU(0(SQ88PtlZQ;iwvhC*I0`iJG6{Rrti9-@Px6;JR58Dbtq8EV~_S@N;%9}43t=mjpNSJ1+PI6AzW{(&wQM~BRGZFJ#++d3PC?rvy z-q~nz0MqHhNR%C5*$GuyU0wiPH}{agQQrM$Q-RV@ls)lud?B)I)pXWBt+E`fe;pE8 zO1=EXO2me$aO728ssE_vwgdVl%k2VbI-yh7gpB2FJu$>4eO9@`EyEz*lTg`Yc%B-N zG^i`=$jayV9UmyqJD0|UIkSI5w-L-%qmF=YXr+z)RX?Bcg6w4d)J%^5twx;>=|x?&IM8egQR&262z zT4JIfEI3d>$nGMIxgkO#l85!H(f@1VwLB0CXU(kL1%Nij|Di_;8ey|?Aj8S-?Z<>jm(Nii zJICSsKv?h)|l&Fk_0FR%SgO^rCBNukek>ANQn;V; zU;i2KjuK>_jt{hMD5OVkgOMw@R?oX96dv4P{Zi4zA$oEyw-!N%c{3M^*TBgxQ$$dO z`!jmt%=9&6(DiTq*c7FBNSV?;t^=gozP>QL3e-P&6Bt00WizD@)dSQSr6VqY;|u%P zL!V!lCD9dvs()tpD7xyObg?=O@ zWxK%DkcQxBH#xh#{YfXBpTR!;c7SMokr&!m}ylkJ^G~o~CQ8`=Na^Ygdde zG*FYW=jGxp4JzX(u1f8>U8$9X3bgUSri-;O$VW1qA_X)%FU<0IVA#P>FFg?1t7or1 z1^ANC>vQG{UfX>X+|4?}N3_rTY?$Vul?|Eg@6gG~2{mJeLQI`T8XXZvtBi?9pEnZk z?(a`71F|e`5^A?5O%lXn`TnruCwN#6mD#D$;e=Qa7TYPC_wg0 zK5N{F`?5JU11#QB5;f-3?gN3wHBoTDbvKrnIV3jbh#rHHH3ZWT#sU%BUH&zJiFr&Tjqmep85p zdYhg=>8UdQm}W}JX6a9|@*C^fz+v4Jm4>AS4^Fbo2>}6(4wqfkV~Z1b(HeXrE;A6h z@e7#+)4F+^Qnu!FX8{f47|Q|AK77EbZr!Ay!^a!$^PZa$cY2U80$c$&t(LM&*8#DH z1aS7mlmd%Wm$&zVO(s9F(?@wcz;#3>M;MqMM)e`bs7l!vj!o)KA zB_y+^Tx>Lf(mvK7P~6TpGFY1*IjweDVO5~^nqQ4-ksHKjpL-=iT>MkFdbvu z@IIBsR1EV4amtYG+f}$8`~?TBw(W4dW3m&RUTyyo?PJB+8#P3MIb?P1*Z;)JRb=R| zB%{xb1)#pH%lZNDK3}L$Zf7ROxTjO8EvV)GXP2%*$HeiR*hzS|NQU3CegA%?w>_8G=qXK~qs$j&v) z?)|)d`y6>c&;+}gpZ5DkK6j2%AaHO(Q||yjzuGyKZu}1BmAk%s|Fz?))^St3TkKxi%>UT_8`RNWA* zp-^TmxKLh*KaHHp4CC9edEz^F!^w9C!=NTPtu|rO&I=X>6Qn}lqXOAicZ}X9!%-CK zTfI2)^G1id8Dy5;VJdmffsIq@NQH^hQkh-L-sAsbgGu^0q#?o14Jb24$Q@L)R=};| z8G3*!1-tc(q304mZZa1egn@FDp^pDG>+>vk4FirD54{AoRe|j?F9NXU76RO>zx^n? zh8rnn^V5)a{b1V&6nrXFXodo2FwuzM_7g{}4&zsnv5RWAu%U{vw!oi}3Xq)x9VCmC zJ&~S;`q2?;8>?c~ERUxRHxnGsCR9qI59vc#{vKdl>!N*48oZrBRE+{a4^lq^_`uGC zB0|+>_``76=9TLyfw;5oREW#!-hZv6n6{!>>H4jPeoAF2@uLB% zRTNe{uO8hIu|Ch8 z&D(FXND`=2(;ThocLo=T6bV;Doh#ir2U6p&eAb>BV5snB%Pzhux~Qg`_mmp zl&pg`1HO1QLFtE}#BV=pd8wcLQlU%hh2^Lw&(k`}Ul8tP6^TG+n^%Szf7-8|o2TE; zc|obb8;UKGFvSiYZ{)Q0*QBYLWU&n@MYE+s!`rW9oSPNoyBH!AP9G`^3?LTk1NP%b zvt$Ii_Oc1LNUxZL2ZE&t)U469Q{#5ho_^g9hg}-ntDgnvX9*|rAdbDEWXk7iyWk7} z7_0MM*HcWjJEeez13gTUG{c1m$EWYf)~ajDoA!)f7<(7YeXe2(4lxJzCJ>=73uwZg zLbkNT@exA_F1hR?XdgvK>E{-=^S~Q%JpL2Ph}r)2SPi}n^@SG*r{dWDD3!%;%TS;>vp(Je0 z9M#i1=P3)|n`#)_;LV6R5$?B(f1|8iIEKc%XDN4!{J99?x5j}eWa0bj& z^dEH?t+0w18Jus1UxEvLzYJ=+fh4W%gSI4$u1UssBUH` zqLKZD#0k#fpkhqaz&boDw!~>U)CK_tWJM)*jdhWT*LxL8)}|n1~543>ko3=XYeuaDyi-^xIu5|*Di*~>muoWJMwhz0gEW^n@be1 z_WMr1JZlvzkX<-T@XFn2DM7jJfi z_B!Z8?Xe?{-E^czKO=#B%9*XF!G+d~7`HS-cig}Js##cd9;j6W>MKKePL7AI zuxIPkhGpNvjoRq&eX^Da4ffecac#4#T7D>7H7oJOL=XMOI!-{bRkg{A{AOixPQ~pT zY1p7R?S0_K*I|A(Xu=A7-2vyQlJw-Yd+iaFe=pxq?ol8Vi&!|N3sD7JLw-q@Qy6lY zmtMTx|Et%xrS8&L#HRKEe(m>jHBdLIMVmoGA)9!@)HrH4Dw_My#m7MlVR51nX}1QI z;GJvx1#Kp6FULY^{yASXJ?Uj8R}BqH%w*|s#2uXFFv!o5yWc-0Pdy9|_!02FKV45e z@NW~+It1b-(^#NU&FAX|39R}2<0d9n?R|F0*9O>;=HmUDcVDqB;(v_~rbf7n1^Jjz z=>K=oM^Y%?eY$kRMYduFFuS^awcCd{78aBZ9NWuZ23}KK0lt{Je%yzYbU3jhL3~9k zmU3QNO6#LW#`Y?>;WrS?UYe4ajNY>hb7hEN*c4FWRt~HC!J%jIR%Wv>l&@?t+ni0u zP*h-HjC1d6k>CJv@qE}YH`9Om?MQ^v6jf}Jd}(e_b)bm!Vv2s36qDrqr5RJq%_^vo z1nX*WHfl%c(J1@zpS_kTO6b}{z?dS58K;!CD%F_=Te?|p^4^W`MBnfGDmO>Y+sYf3 zilrGUaYpFowo@yL^5D*u4aA<2(fFV$s+5GEV8a|uG%paSI+&??R zQ6qJ4gzp5O*kAXWgzh@Jjsll8K*jZB)7)RWm2JxA8Yd+GQo>iO5F(9DW%Thm`qHeu zo~}`_9sv&SCbG|xj5ddvIstOJO=G|B84P`zeUvS%+jwioTko#whsFkA#3x6#>V-Ef z63~P1vg`TnI|M-~OZCz&Aa9*bYDklZ=L{75VGp>XJrNvsAvIfXI*735+>GTk_QIU% z#(+O}fjIYm(Nn2;e3ZVVwMGONTx47_FJbQG8hvpA0aE7E+#IBzUhq@F3vFa|N7M|G z>zI-WSXW2@8PF|!cM_qLAKl)x_MphwUDZ%c&&y4xt*I~Y^L(<$tXGfAss~vM_k)td z@N}Q?F4cFw=k@mjipbgA=lWg0^%70ri|!?Rv|#`tP4!zWVzkH^m?>Mf6Uy-)? z6{on}1sqeU^GJ9xacuO(L*OLuyE&Z^woVtHa%=_6=?r z`qr8HD$X4PnyJG$U(9#U@=)&uxq6|44u>$Kd)Dzj&gynQJ|`w7A08}rWf79p@%@z@ zUJW_#1%^f6KnH@6*MM47!Beo&=){R3;|*k|zK%xOADWXi4oRw&Mi@M;o`QAd6!q9~ ziB53gL<~2!o)(k5H;6YFZpS?)dGF6ahG*cmK1})XT^^F8-ACVNF~^tc51@1(&>b7B z(gYayy<{o-*P@5Z+BNV`F%?|1-48J9Borm}{P8#%1Xa}T1`JUvRsREh2$Kz@{nsxD zlgb|-<>!ILF}{gd*758wsVXQO6*G2%bTZp&7s(}W%awuL4?~d>)&|p6mBcSehF*Pt z(F;MVre+h6&Podd#G(_e&yUZJKQ$|PNHRS%kK}#+ZP~p|Q^TZ*MT7=IWisWlzE|Qj z^YKZDgXT<{F4rIan}R^@bTR%yZ?a1%MT_;=2(&W^Hr)1e_yxH zB8iVUVa6{>VEHoelH!X0E6nWb6lM<^hGd=XIB!7Ye!iSo2dqf{r-Ml~yn-1kQL(H1=&W+SHTPqddP^=0T=hR z=VGenrZ2jyt8d;qz4uzrT4xp4)3MpxRP>j2WvIf@4+Lx5FigDyq=dy|!^O5uIx?C8 z$G0lS%+Jsrq@R%um08#dk#vu<@O-sKDGwbww6Hf>~hK3U)` z%$ip+<44SE9tjBMAz{7) zzsnu)fp&rx3Z8=ZJr8@`0T(2wo?f9sx+OGK^Bao1~Lktd0sJ`FW}oRMM_{bl+GciqNSSr z$k)02bP+tbw()>`&cAT=UTpO-I#4Edjq10h#>~D4rcVfM6`17tCW+yMtDDQmlX77Cf&^($9L3rhDnh;g%iuOU^<+=upmHEtw>$TKFcDuN1rjw#2uYJjHG#G;1 z*~3hI^H}0w*}QDnigc{^0&S1HzCVSd7%!78fi!9R9^%B-lt%BUzbaa>o?FsV&Hz!c ztLRs&`n2I`H?`RHd1FhmPjWU*1>*${Up}3m&anwSdAeUi+^D-TsXQP)0$67S8Y7j%y7VYw}j5qv=|WbiCGd`jy2iJngrPp_iG z7sBOgADa6f;P{zLy$?J_oL$6YJBVKM41RSjx&-gpayY+$zoZ6q%sLQVtBbMRLT?iF zx$D1-Yd?)j^UrPk2DOI4SFQp=Q4r9z*B-X@I7CT?22<44n{8@=)B)+0CBOmU$0VYU zVAre`Fj>b5*iaY+v8DekJ+NRgAiLaP9a(x)78fX<$BO=(bl;(jl}{Pbkd=3JfHn)gq2%Ho0IVZi zO9Y5vRIHS#Djpxkok=r$Sfcs&mHV1&o>08MIL!76a&(<&eA8{V#F=2|LL@b`n~2qQ z_dt{}7I{ivz%SD|{?q>~Z!sCCNIB@Qp~3H+&ctL=3yp<&^wjN{g!}R7IPisL|FTA3 zv3FIlF{JY`^H=>q{)ZQ$4qwBs#9%{YUH1+J5d?+weG{1AMr}&)%SZG%5OTnEfXw_A z67IxiRv1E++=U88DJ3(B*__y+Do0gm48m7N&!CajcF!Q}&xK9(#Z=Ma2IA)D3VZ$C z*ULvTsK8`_ngon5%^~%=^;2Gl1`io0l0-u5iM`HNOBj)lWVCjg9>^H?$(o7a8?4vx zawec!Fo*UV*unukQ0R&ON^V3(iSz>+k0uzw>c*R;_1&g8?nvrz^-Fw$jP$nGs+5<# zL*HYVck(Sg4|E`42*eU_N(eZ5KD-2ej6U@3AW{p5H+>3^&`(VAC#kVSOk4H9xV6TO z42c%Upo1D3h3X}ckTp-AA>Eh=WA0+}#~_4K-W%?}AJ#{PTcFC{^e;=>Io-BEhd06V z3`CB>z7`@{JVFXvc1Jo$#6-U6qPvWRQd_uLhW#=`m`hoBs^CzpM&Q=|b-~x4+Ghv} z6Hik_*;O|1f}JuxTLEd!uz@vl@D)6c06 z`JhZ-8GFOz3&tHxT0IbhZC3mAm57TE9WRy6DKJjP!$m6=Ag6<=L_o7KG@$)E7&zq0 zqkd-=1vz7SetJef1HEvHQ9wsB-Rn&1<~VOQpKsZvb<%1jqaUe!;JzX850MuJm$gkM zei_Aj&xknH^mWwTo5cAv%JHOiud(|QJug-&GEqu6SqS9vvI@B1a~t-z0< z&Q1a9A6Wz{A=#tMw&$V>H>`9jqPv8^xv*(3<;CJW3lp+xYwn^P;i2l3$jo&pUPHTL ztZev)+DeJ6L%l2S!p)Fq;&3(}_=0oMNe!&=XOeKE?LgiyEYarRS565=fj9a5nM~R z#EG}?%}Ys#i89R79MSDs9hBoxF&v2wRzy-CnxubYT|>Rz7m#_3nM+*eaXZ9sd{MW# zMvuY0X!Xj+5eHroTGqJMr>p(3>NtkYbjN9yf4bB6PmeP|V#r9SYyQ+Csb3-ZEJ`e~ zSF+mVlE6&yc zF1$WBGCIg|MX*6)J~SU`>XLw{ERdSWoN){mdr=Yb-JhM`+*M)Vmqb5B z$eIDJ+F6GfVOOx=J6-fJD7qDkfHSyU@Cz z#HD3i8Pz2b3&QJOf+d2N^j^T7ZQw1nnz!TNffulvXUh4lWn70CrEE5o2bseR{O@Cy zm4?4pu%;Uhlr}>Lb0S^vY9UsiDUAIT?5$NUo~-jQZZU3QLR|MQy@{dQwX}Aqj&CF{ zocK_t^&{QMkRfJMPomapKjNuZeesRaPYEAV&&Yk|a|6|T?hABNf<@@`sZk7CJ>anW z9uxC7XbE`CWthLOu0dSLov!Qi9J)MTmR3~7@5j!F{2pW^9qH|pJpWSiO}=KGC*z>} z{)HATM37UA@p*r5Lk6Uwx%M_2y3?Tpt@8&Q%>+8wvlti)UQ_BOf1UYu;zi5v;0Rn> zTGC>tUIS^j|0o-zcag*|h?Kr#uZGtoCX0Lfp{u{Y#BrB$rBoX)GL-gsig`Tn73_iz zn9*DY%^nP(`;FlJw|kBSLj>k>Amj}6&SZKSAOe?*5s|lp$o&;qK_6&$J2!q@{PJD@ z_w#4)p2juM+h^$y_(MUhs!|;p(2scgktuGSL~tf5GnFE~rKHZrJh&6*%65aqUGNVm z2OIa(bmC#Tn!wAB5R~MM zf|GId;2Q3r1gJHhzzr{)xa19eopn}|kRp9g((JHWerq?wR|diMP6*Gv-G{lxQh)~$mN5z=LzNgfnCtNM_-QMZyyhbJetUw*yR6iO+fgB?^`iJ(zx)I~HT1qw6&ZBdc3}t|#p|4H0$R>yy~M%Yy(n1*8JWB!ciz4W=Z8 zCa6Q68BiN?lmjPNzBIK7h2Vy(S9jb{JueP3i|psKxQCU7diYr(u?f@EK?ubNJ(%>I z5jglkI%Azx%c2*wZ}7POdkjJ3%jdxg(IFBBw7`-FSfy#x-4j@NAAJoQdP)vgJ=D0i z3o_}UqPkL35|lm7nfiyi$NbSc^Y37dQeE3u@C>!M_AR(HI2yW67tCjjqHv3A%mj;FWK7%%fqd^c4`PlS;E5;khf; zQM`ZaXv>*wwn+7%%}5n__tLJ+VA=Sy6@U1F4@cu5w?j#o5z>4Fs$rQ~fukx5T%4Bw+AmggmxZ-J!Bu(TNa@2!gOk>wR7Uq>?C8^e`Bx{2oa zv`}!c3bx<&4sgkiNT^XA@NvoqHSIWSHg;=?Dbz~oQpE31bQVC8zd<|{79#LI zDEM9WHgs$TQ;H75x;)C>9A;Ooge+e~-})ruE~46sMPzi>-fmaS4Dg&5#&F6(U`%oF z{rd>RUJcX`wer$~e(P3NM%T6@%pdt(l5eoXf;AKvGa87{-iD@|`I$K5lEoDQAY=XW zdY8){6(Zp53^kO_X>inm1sXWeb!q)S*VPn2?p0_{4Rs{~gF(E;2R}!h06lvAGE)kr z6A5j3#exb;;}%+@F77_DVJ^PD7u2{H67k#t9eStV0WjyKP6|9C7JviPuyafw4R@WD`(p8aS3no`v!oDkb`QH#EELoEdI%nj zwwV<&%z9YJc8B&KaQ4235y~9IO%~R?6Z?EH@(=9IUd{M8aI-!yTLlg_FVYaPHZ0O0 zMKI!=W+3LO|L%5mnm8y+4rSz^^%wH}YWQysTDvtL;L}?Ud~nB(%NzL1LcyDp%J-3H z4)sH%V8j)_Mtaai*&~-){v)?Q<;3r2re`s!kltnB4mtYA+slr{|L`i-;GO=?F2T94 z8Ch+(n`QT3b)f_pNBx&Y6Y7dy1tg0*3GBw zFU~$%ORS6@by_?VsZrT2aQ~Pr4qfld;r}_7xaw-d6`5;?Qz*y0%TyuxvUy$#XstIk zewxsDgv1UAn+y}@VY49rjTIo*J*lL$T}N7!sF=)*oJc4W*Cmt$W+(6C%*dbNMRK~O zwiZH*Ki=w{G&sO~-g%JDTweL(_$wGM87E?{@GKV-N0;;EM=FCTf)7ZsV? zgvk{((z_$r*mogvc&x##Hs|IlT%U}Nf6&FD0%0X=aQ!FWX;83U4G4hrP@}cG1buQG zbQbh}U4`2bLp>bIniG0C=B~7f>;`0i#LHxL_t~z{LvWyas)}s4w2il4tmh1bzu;_O zjopeMW}MA#vW{j*lClr7(|L4rpocK}8h3J74_S)s;x5GgJ8sw~_?oHdsZ$oG*7&o= zFt|1Th$X>Mo#)c{bHU%*@*Se3$^9c70vlXdBz8M(Ym$yFeaEGG**pb<&H(Dd-Zxj! z{gyX*q8Z@verxRPZ3=yY-LyB&FyG)6*S?45Mf)jvO6T=_>@Hh-?_!akd}h19hoK73 zC3LpeU0nSv8F6|kX#tjQ06Is7c_O&LMPKkPakJvyn|;L#?k6 z6k+C!H7Hm-{X~|!0L;ST|0r#Veal}-z7g_jqIlRa;e9*A07iZU%-*Ar$qxB5G$^`< z-6-%*5Oz;1pdiXNUQq*Y&m*Xz=Ij-jEyRQg2`x)>#~kjFVPlzTZs>K_)ntp)f>`t! zM94u#lc`>(Q}~-THET|ztQpTaCRqX|^c1?kOxQBBbMQ{!#EOdreN~N6b!y9Sj~o%> zYINWv+xAI{o9{G@zmBPlMXAHtFT3z8Lu*Shd^Rxb{f5kM>o)pUelogut|{W5`%+<8Ibf$`|>H0k79&x1YO#uIEf2%HT_A?CT~!*nfe&bi9-V zNh^?vm61G+cJv*=t(?rCN}c~TxglgUiY>Fz*S>u;rHv{RYG=t^C(WcD#e{1kWZ+meHyGVK*E)n%N)YI4IU2< zB?7ncle9jA{S_S1;Ve>n$YdT?z4l0nY_?*Xvwppy9pHqm3v3+t&l4*FIN((O*oha0 z96VxYNs9HtlSvd(VT{3s{lJ3$O8m)NMm_26GOS|~W_*P#?DL?BM52cHT2HgKs&oWPR78c^v3^;+ zyb{*x={w2q#Mo{ljOPNsFFLn3l^n%9i%325T-UNRofDh|nFbmxZM(?2%j(#gt^5(O zOVIENsQ$!M(nhQ>QM?;5f91!XZuMa7-#c093`~e%m3x@MK;?uLqo*Dtw0Jmfl+oDE z>H0LgY3%Q{2xWn{c0k7Jb1Cq8?5n{e-nr__#!M@b)OIfkzNM**LVefhr0KiHQRz2hsWGT9Qf zv)r%~)fzw%M(FS<#u0*lCD1kJ`iw)8QPY$7X@rM4*h3U6EE3}U!F_@pIwSTlOKO)wz;8G_$q-@-o>lTJNm^zCS zN@h=MD|$_;8WqwB(#ABld4v-B6-b2Hk0QsyUDdHl_ji;R*$tKL;|Os3eC?LAeUS%Z2~PU0Ag z<1LkNOl_Jc69|b@k!Ee4ZRTa+)7u<}W)s#evhaVd4h_aE|8D}sO?Sl$#bToQ4w(R{ zATM9bVkU#3%TIQ6`H*ZZ(m4)TN<1I0s%lgOrv8RbmeMX)Jc1%Ku>{kJqn9oBE^v|e zdoY^e5Au>D#g;Lfi6YLrSQ(lB#R1_YYV}j5YxGH6iAsK;__lJ<(;*UmBg^7pOBdv{ zGiP`>9z68w<>gAV(2}&8{Chh0Ls+#nH5;K^AxacAvz%SgAw#JJes^D!f;AH61`>A_ z(bmdP_qqc|37R@ES8M_r#4g17xVcV;t%Cj_8!+Cb zdY#8Akx8mZz44<>(`c{twhIlF59*CH644&`WJGunm_I|Aa6tHBWa^_=?3T=%0#*w?PrsQu$bc=;%p2AFp!B_?#&%a$HSg&m&?W zKj&cW?SQNt6X@l&aV&UCEfs|gK8m$BugvXcOL?`x}m;HhXiOuN1fc(TMD7Xt*rqpZWd}wEpZciU8V^5xC+M-Y)c*==E(C)|%yr<- z8+o=1mYJIZkLg>pQ)WRKuNR1EkaKIcWC5n4p$D0s=_8X!;aM3tps1f#niWTvx!*BV z{TySm(S;H>FF z!PSSY{7pU?WzQFJui${P=xDlb7Y*q+8RCE zc*#q6NHW8&)90vIJH8&Oe{(L8zpshtQZgIfyZ*Z%ZMKKEd;ni6)ikov|2p6(D2gk6 zm%Qs3gQN1h+X1q8R{Y@zBRGtHST44GE83qB4CurI(lQWn@n*>^=%E0z{0J@I)6nE2 z4d*u)4dOk#Y=g4Mu0*5Wi@Z>t+!hJW{TAK{&eH{q)XvIG8Qio-W_@B}74r!0eL`Qe zr69MC5vip6c>5tXV!pVXmEDtD|CTP<*TKZW@5=Fe)aL+idpLc)ANKwRurfrBA453G zjdpS&*vRv2;cEA_VDaG1dApK}aKU-+`x@c$Ezf=;WM{q;^@nBu$;1`gaO+OnQhSd! z4fuu&!6yP`*5ow_#pT|i+8#{_as1VWLKNw^K&Xt8M2eYw)o&cVD5scIRIlX zncHS%-WW{n>Xf-*^#KSrYiox0G4O&5p`qGFSJ6@?BR(Aqd14sR+;A{GOGQO!kEt{) zayEz?q&gnMn}h&hfPqVOCWSW*e;+1?<&ye6=y< zIN5UQHqF@oi&t&GQ$XxIHj0cWQc>;W$|-QerLzOB5}m+;SAf*F zaGJjXUFOIW{m(yk_Nw|ILhRPwm@3-T8AT9V2G&ni7yCzX*lmsy$d%sfdeXa5BbKV&LVh zW+cKU=rGTuO(}2{4|+2WH);x1{8xIFA~_!v@13^Gv3a?^$^6U{u-m3>Q`h)88lckc ziMatij8gk7sq7P*plfc}?Fl^da9VFz@3UKvY<{YkZPUPK!ZZ9Axz5buV7&IeaI#*7 zjDLqj3oF)Ts)m(Ej@f%;O^T)HRRSvNc9DTw->pvw@A(2_*Zy8Wv-#@cT&K*(YFJ5! zulkp0M9(EV;9ey#cb>kG<8Z&7q1Zy~jocM)Wm36Iu*ZC_f_q5$I~KS-XXQ@ z?G0GTw<|5ET-JLME7a)IvA4oMzQk{3_1cF`@5tinY{ksN)Z^8*ID_+8mOXc(e(Dzd zZb&+lk2^f#e(BGL6@KrRa-9sRf=F3~GET2j98+@3CXH)f-h5DeHSze#+%qo9UfjRN zTQpA_(slbsI48hg%jb!;NU_{ars5f^QyN>w%2H!w_eUJOs>Yj&)gq>r^t~+qkUx)Q z&7+xsrNKC>ta7m6hPIq*Qj`X(p+LOB`1^>yXLOBs__}L4je9PN+TD{f5s67VYE*@% zFy2SPG|=5u_<7l5CFU0SXnYcwpQakm%`rgW z&XCR1v2KOgOvp*qDH>;7{zqA@H5RMUV7o(*l~+7Z&X|qtDNoI~;5`C}U%x3J1T)%GCf}f;nve(^7yjHzxZXUF8E3R=K-$nFGVn zCHygMz-mV{rQ*n)Mhs@A5&5fqmZ)Ami!I#|N7Z(|db>m9f-1aW?>^-X2>qG^j*|@v zt9A|-mcRu~KmdhX@Sf|^K1Au5ybs>?c?!xy@8sp(dgpGekn|z@6R{)vT#3sgFm}8v zDGv2AS(MTrcD9EApGX5^jkOzy4HftNUp{~~;pf12(u|J*G2Oczf4N_Pj-QXPd20$V zj7x$=*l6|m98ilHCuyq{DObkp9x+1W*ijy;2dWeycylk%ncu6()Bei`jJi+5Pzf6# z>|71<7KA{}Fxr1;o$n)=_-(x}wxIbCzdB&Rm(e(5A^tn_hrT4p$3G$eJrZ>naWDt; zbVoC0mzA}-rZu@YTMsDZ28v#%g74AeeDT52;Mn>wXIWcF2TW~@xf#_#E?ReDyo-?5 z$HHqx8VmJ7E*SE2g_o3iBhPy-jH1Y^w^6ulOYpK8fcMu~C5Z*nqIkDZ zQ8Wd{r*$9)eLvoajr{WKlHkjy;P)fRghovb#2FXFwTmUUI>c|<|2H6TfKEI+{Cb>A zK1>^bDVP`Vfcok}3$?I=axjT~D->t)mB0-8lWSmCt0)UgUs52J<7_hv@*H>%OtJIO z6hElPdyDyMNU(|!K849Z^iBA+YH}{U20Mc#HmrySKvG5UNFHL_y3TVBz?2M2I zL!p-9F9=DJ9IQ^FVaE-)%bPdf!{=$fL5{kM180oGjd;jZB!Yf|lOEacf8OzW)Uu*u zW{-_S)oGtk*216ti`u3rr@3KSNyIU6K|1u|fFzwA*)L1Dc z;?C8eL2@zQ>(dBQ&=!1ska6{HySQV%Rn&Tm6US-I3%I`qO!^qr2ce06 zE(w4hgkwrL#J86!H9b+*w#SXK>WY*)Wu?D=5V8#G#Czd!6Jq?iS*__cyGt!cU1afT zuHuH!vvV<&-sGPRt{2D|8Scp$miNI%@ZtYO1cJnDz`V{o;Fq*(D$oPGoIJ7k>ku4< zE{^~HWV;Sv*Gj_39dRYZgOQ2!bsGhS-Fyl@*zf4?E^K@OKMIp_2>{JE*^F$;yD9UEhA%Cswl!znys?7_MVIV?kL&Uv359v~^%D{{Gk>r1v_2`f9fr8B zu%Q#rT29&{f#lS&|KsOG?hSR2$3Dj^YaZVsT*g!|aJ%Z~1NfA*{eA~B;XjEj6(7)` zg_Dm|A6{{`VcX32ovT?`zFY`<;>Qa2Bz&f-9fl5tqDlXu8b##v3yC)DmNf8a1d+pk zH@ANGXrhg1_ZU7P99(FJF^yt_JHg@l&p7nKgg{Mn1!}j$)sHd~3G+ybhWyc1^PPEZpwE{v;9c_Z-{MWyX z2nt$TnKc+38CFXI+JBwLT`#4>L%-nwcgKzW#Zp_j6kZ=ulJ-8ioI*sQ+F(=l*Me+n^i;4;zPqJPdW0 zQ^~JCKTm_w%5gmiwX6AfX9&|QsMlrPg|rP-?Rr7WKkm7D6zPQag)@qw=^$cfC+;y;@o``<-(UnYN%S${x<{8t z)^j-1UUv^#GkC6eaH26jO^8^DH5PugWmAWq)u}Nk8SkR!@q0fr_|!QR^$|?XQB?yh zoCqc&CvCeMcu6$`RN{fF-N7!1@H$=K2awpS7Q{(N{(s+Y0?PsS)W?FwNZMXq{1w!G z6m}VLRH+OW@wqztj&dl%^(VYQp(_|(ynH5!71pxFfF0}~O1nm+Np5t0MnO#PM=3MM zFFpDC`@Dw&Q>X-6Wq$PEm_8v>72}9yL`GO$pzMBHtBV zY-Qfu!9z@=`_5#B&i6~88U5+F|qn)L6) z6;junkn%u8%v&rdr^BHvl6B8DBJsMP`Db-s@P;!P*e!NP9hfrz7UkDj#-PO@Q5n;k zBjqOxIHZUaLqh>bh6MIvsyZhyBVA;c=pFa|;tx*Xej@19I88~Tf_0V;wo!FQwl(|J z^feh{!WxxIkzlqbA}OkHD{7U&5sGEA*FWS(YEz1}0K;QyGOlxczGlNIM9YqNCr8s) zga+?m-ASqHycZ62R^Ee4w;PYZAM)Oz;HF&auPdK_UneCQ35nobe$FdpVB4pnaxuE3 z42DVdfm=Qe59A#-AJC&F+wGjt5aAo*!h->angt!DYx7uh@)V-cmcxbmx>9hgVRqrd zD?{u5!bGm^Cfr2c(q^=Eguht0aWlsM(<5!Jk9hs9EHX;r%$;O~>5t}@>~`w|mk>B7 zd4ajJ^D1dKx?zrv2#cyeo1SAZGfdBmVs%rGg#cHI;u3sHMY>tJQpyq9c7$)ApisB( zVNp2aQlNgW@e{iF!i;yPtiigkC1aM;``_F{urn?Qc6KC}$n2?Bb~N!5=@86{8GHz# z6456hLXcVmzx|cibE-2snBGVQf}34q21m?0aR!kwjwf+UypS%-+1fKEswFaNQ@$Kd z6pfuTlDvP8n<;(VLywe?{E=adl{TGPAvl>elh~y1TCln zf}NL9DMBW{6PWo8iHu}*oo@uSHbzfN?8>xr?EDJQU(^!UUz-}0V=`1HW0AjwqgwIv zQ}4RvCjVl-aK|l^mp%tOL^gq;&swioR3I~+DWdOTZR#2iJ z__bPV;s2v{aQnqGJ_>90GdWf_Ns0o%FQ4V2YrtkM0X|Fr#X5vHS0PNC^ZDTem5B}R zuGxC-EZS~A?3Sm*cbyoM>}LaUeEE@IDK*~;#r8?=6m1j6;ndkVJ))1XFt~8nlw_Xn z%l(jLOXj$n>I^baKV|E6J@4Ly`J$wRgqO*XPf;d~-3mQp`H;x`SEhvtc@XIamG^ai zNL2_##T9);g`PYQNzs8$5yg*H0G$f~I7y%N-$X94>{z0?=%B;SoGHhW9dyQ!F-LKj z5QJ)1mHyFNBoEFa6^QpDCC>Z9TD>o&g0EBvdXYzH0`W4&9jTSM+N(IR18c^VUjHiv z4fjANKbwP_9bBvs;JPPW{7>em63vn3Oo5dA|< zKXO1@E$)%zWc*v0xqy*T`@0SMRIRTH``1wLvC@wwA&cZuhkNzyl$_!*fA!5a=n1qE z;y7QcB-zqGBvQV_hsF6d6tOvyLQYU8+DJ#Oq$HZqyEE{&D!ECE<5$|I!n~bQ_PaY& zhh$G~>c|#l(q*hAI3V3#0kjwZ3KcTpn z(rn=`O|{OcC$LQ|$ki^wwvZbwvOT7UhMG}=Yigt2BAInU{!x&?rD_Y!z7RVOdgQ^H zfWoI$RMlWkXXpDkgkPsk@%O4*0<%;38}TPAf(PzWet_r29D(5H)I%ka0>_!bx}Lyi z$iNehKiwi1MkN-lVusSJPuz~q9`hnu^K(vAXZNP-1gR*Jk;T6v8NeQ~6&G#x3JGlo za{m|FCfv^AD#TSKd@L^--jMpA%yyAt&h(~??M@bUOmPZa#x&ghuKch8^Q)8{j5kv; zp(cJ>iEPXozk^Jmz<6J!B51Z?r<(eYHN6Bbm>fk1&;e@PSr5LX)o^}}QsoL_Urtb6eAEWbIT^MN74n8 zN@$R~H6HO94q4hQD?;^DZE$RokQpnM9p304lR2)FiLa2t-<-jyxo(v3w~DRb^R{zI zJ=-L~A=;B*en~*47B2YaCFveS{tGbh(^Kp=@ZtB48kbnw5fW|snEHw+w2lN;ZwKF~ zgQIwBaeneN^s9ssw=jo##ccL42sQIxktMlL^>?z3YqQ-awkE!vUH~j|Kb|ctsNb1_ufh z>^?FHJ&9wr-MfEqeOoZH=oiA>Vq+a4-y8_DBiNS#a6(p+tFZT(T+8_ve|NHH-J1Ob zm$2ENn7F2;EkDZrm}4r+#|=Dgl*~hwc{_JHVQMBqV_wk3q@u-Nwa`gC?&HqtEO$gW zeknZ+)Q|5WO_<-t}UPl;@38C7pe{jElBuHvwm`icqj#dfCLJYfM3S2vCq1N?8 z>|g0XdHK|0#XFKG8M|_KJ3}!xRtZriSRc6>`xDzlnK6epaVkxI`DiY=CGx4ffG$Cf zY9wTE4~4CqV2M33QW+}TLKKWIjRMx~r?}NdqiJg^Jq_`i#{oGj9k9QTfbA8;LL-_0 z3yd+t2Ds4QuA5%K82DIpSOAckPf?Jk3FrMWh9%b!yT$SBY;NdQBi4serBu_e!K5;w zRqJ5jNQymphZwR4v!5x{gcql&)T1q>#frW7Agu11O*EJpXk#lzz|Tetj3A%Bdga9$ z#vn4KBw%>n{iTAvaK)|E=c!72k+56bD*q2x!8!c_gyf>5YJe|A5IRn+TNGgPN80f8 zBwtEwG1N(;keuQ2ecMal zCC!pbV|k6ujOLLeSPFD?ljxhrqf~@Vj&d)9gO1L&jo6m>FDMMHd~Q_X=aAgIPM7bp zO!osdFzEgLa#`s%0uHXMEtI3EgvFVBs$j1|gCliRF7wmDXs)lcdk~B@hZ`PLaA^|4 z&sj|WL{6yAi^#1C!|HVK&2OY$MKC{E)pMa--%!u)7`!Av8~of7zCW@P2WH5AVX*DtQthC~pUXvIVtt{y} zoD?bH+|)8qQwZf-CZi!awi^XY4NB6@@BYZ=0IAwczGW36&;WI9Axp*scxVq&g98-% z2Ko-tBQcc7exa5%Z&#>hp+~mW!ToNc#M*DV9R!7>!^XIua4c;i$rm9clhDhs{ z0^T!aBo98CQ4%$^uv6FD?je(}dn7*4^sk^+D-4}y!YM9jd<Bmpd$1xm!NR-Rv*0%*^EW?xY6Roi=i)Ta z!HFZHz+;CZ*lHdl5R8a1+A%*W5WBk!TbDs);J&2Ia-KOnY~$t}H2DsMvjIKckE~63pH!aULyFmY0X|S)`u*Sb9|2a; zb@dQ#$8;b7_`Egn#jBH+X058bqz<yW#eWex1VY{1u^3{qj!>~Tm%Z0S+P+K17YaG$`Gq1o zt|UxAC+g0wz}0XSz)nTu+dc2OoC!02)4^NbLBgwQa-Sp z2;K@b6k?S_uwybbb%g8r5*nc@m`|`OW&p^iaxwP%n4>?1G;ytOIl7`apz8Ul@e`y) zI;uO(KZ1M`Gz+DqQMY_V<7>Hk%!8(;ey1=~4?9`eh#GO^4elri&&s2JwSHy#I>nn8 znxyHPpJXS>NazaMCvT1YLREqmhrdbr334-JV1s^fgnm$RQ*xEppdC~ue#4A!u9kch z2jv$({W=Z9q?OJ=O5VM&1>VAhtj3Sq6ryp{W^1%C$8LG%N{qolM3YmlBOJCsh@AgB z=oXXk$)J-;&)xn<{@=eJTCOux3}FMI-WFLQX?6Dr?{R9Q4}}9tY?(XHpr=LR9Yhc* z(BXD*2h8i^aRd^%26{B=EB~wyzI#yXv%A??9sm=9bk@laHAvL#&WngJhh`%StLtF4 z&;+dbGANrYo3ZWxq~n!$h0jHaQZ8lrgRBA)$+r(b>oTJQbGh7#+@K_sM<#<62-@j6?TpB3~p;;@-_$!$UYir;FgDn-uFH>>*A0*;>h&Yni=DivVyabRstz2F1oQ)tii zNuzb1M~@lP4!JULay?Pkrz_1`jJA20`#stflsX_XKy=PXK^1$ogYmU^T2= zLgVScLQ~B8O)5)s1}}A$M_Iwn6ek)@c-G~+1a7=|>|>7v?C#d+<2 z#%{i0ZF$v%ly-Kqp!Bxpwjo$C_be64#wPjHUH{ZV5DiC-^8WtULpOi&5A0p#*z?x=eLaB- zBlNU|s!iG(nbW!$U07NRoAALBNHa)$*=Mrr&vrQCKa+Js?w)^6SnFyZB#ba z7!E#(@cq*G0|y=VJ*2p z`PHjnT`@-2L4*kH4?>+tN0Eq)?fGNA#s`J;5Y5@2uKEa{DEsac zO8s0zhU8gHzveaGVgV3E-FGOrNchdvupTMV|y=Ipnk92jwYbh z0(9_CfKt5V)goEHFfu*oN$Q->j)l#JTG!>?XqCQW=%;E1`Gp2)AJt=Pkmk)WywLF> z)rowNv-MZx6pOx)E-swW-6@c=TINn(wh6O0`MO(JLj6K%SdKg^9A!!n*)*Tg6L;C! zBzyUYe>C#wV%tGLxA?qy?V||<9vdk+IJ8a5K7@^Wtp+V(ZQZm_CB=DdWN>>$eoUFM z#)VK1ymD5WordVm^@o)l$_p#KzcrmszcY9ql|q1Nx%CXadQuGy?s)TE5Z@U&8v3Fj zt+O$SFfHN`BvpYbiS)w(I15%b-6-549he!^K~wHt5o0BC7_oeUejLb!QL0Nx+ zqbbLKhuo*J4~VQC)`Pac%(-4f5ZZQB5ky(j&Q|>#$9c%Ow;XrCeanOL+X0U_=TixV zAXMTN%PzFIDGa(emg(>=RB|cW-ZH>UJo$M|tg!qCGP*)0i5WmzGQ5*5eMNPz2;x`RCfDzWh` zemmZO{5z#bF1#CI-=uqXKcA{l_>-3F@^6l3g!U-0{5rj&aUJla0m?0ghDz~sZ$j18 zd%aO8Ag(ntfVopn&_7XZKp^O0|8;qTpS{{{VzTz-ztP~z&-VWWrZid0sIDoiOm5Tr zcdrN=BI};L%KY~#_Ww5dZ~WyQu>zSj%SS>T@pTLe*P%NldetgEso@>iklPHi7wUgA`*j1Si1 zpU(+w_5xBxcKY_ogSz7y{OPgl#$@et#c`0tYk=?X)izf&^9gayeSc0VPzMm+5O&)o z+5rzGv7Ll5SfLdbe=6x07$y@W>(sKV4EWjrAVbrphpmX9tu+`{$QD6ilep7!X2|dnaUOwMOPHjXle-ZAhYn}0Awl<3=dZU_AJi9V(M|LnbeciT3yKmI>o z1xjy!u~UuBMFPs0htP#LknWT~BXdOJ6j=#9yn=8dS&}rjC_?T^RAEF= zmb8HezsS+_aEM$@q-~(jzg45UHq5W4q6)|59St`fag+;6(O(~KCmOT@6%5O>~>W7#I_tNgi=bLHAoDgR1D>2<0R2jMNQ#{0mV z^VJMb{ShyFc+yBfZLwT=5rqG;Dugg6a)pT-U8=xy@Q8q|ey>}h ztM31^Zz=r0gBSd#r+J<+zIQOP#-_O7z_(`1tG8Sm%gEm?-T`}_gWvHExJDH4JcL0* zbZG(oCwjHUdbm&ymQTc%dLe%rLK*fVZW0A(bODizU|Yoyxh^Q|ks-9f>;R_h2<#2P zB?D_5E-Xj74E+tP1=z%sKCl3~C$D@6GAz-=a~$u6Z>&OuvIe@G!kv6X+=+*7(KNt5 z5`H+BTC4>1W~+5}(H-dUu1fti!!Wc*ZcA=PGz1ST%?Pj{?$rYnZT6V$j%^Yu%`F={ z>HZX@j?bJbz+!&O@n5e0<#y)7&;ZKy|K862VP^gR=HLbY^+}#*)c>(&koOj2g@8)O zws4~x&nv>pN*%MY_9gaKclc9#F!*)YYyb12-5(4;w42B6-mu^KSG%D?uc|x$`S;${ zRRm+@n9wjFqXpk6WhEVV`h#BQ-Nm4D)@|hAy4p&PBsHchC+Xy4_iWVuBkc2V&p+K< z(Tjla^sVmj?A@^a%V5~={MhYue;oew^T+=1qSwhQH=j^~0Zy>l9}arWZvXtOHyAcg zI?a9&xj|sL;Caw=2*jYw&{CNzB^7g)Lu-TeU~vPl*_Y=~7HrRu2^mMhu&?kkD!M;t z4mz#j$Ifw4nf=fT$ygNo5idUyJKZ3W`7c|?3(zM1lA)G!z^QW|6@4o=L&iN)7kl3N zmaOwyuF5~2o&9*y9yZUwCjiuYKI^4m!<~0;%)N>Rs*#EI8Ewg8F&y?5B2X zFieS59W^N*+LiKLOEhW8d{Z~uR(B|>-9K-3yY1ev)9nwM-Bx>b{WH$AnX!=~ecT*0 z-!=QyNyiSe>7i2QH(RX%N;-3I}h(s@6e^;cy4;L%JjHgA^a zI`@Jw+aq{xCb{z5a&J`4bN)R=d}H=v+j!WBpgj5HyaQ?pY@K1?>i@@ z`4a#zsXo&@Zk`WPj2Akcrbf=nyh#O?D*5BZyLPYJ9<=))@cwzT)h;qU3x@7PNMyO- zZ%&)%D~3xBPDlx3{LKnol6I<6xg0b*YDx#E!osy~aY4ew1uX#E=Z}(d5 z?x6W&alc7;!)B$|Y<*}C2fv;d6Nub)l9`XsPMe)vs~n;&qzh_Z3_c9|?cS$$Z}_g+ z`q=Jrl{vOTv?;(M1RbPvLORmf+QZ;$5AmxHKg1>ikFm3bbbc!>D)bh)UbNQX^*rgc zyMtl#yd#;o=$%wxAV$rJel&ej#KivoUPUI}b-KsH&iSx;eB5jI`|WxvgRO} zz?8q~dSSXr2z~#>c6*VV9@;{i`Q|sXAgn2FITod+A9&Y<%~i~7NuKOjH`Kr*ns1U9 z6Y+fP;t@~)PfvYI*ytzkKo$jJ;4XzZmT#-8VohZ`a-kutO08fak~N+k+wv?O%r7i_>AR4GY%Q6jLBNIa6v>%Q9Oi2^o{k+-mm*`lYF1J%_Dl)Y{>6E_SFM&Yen|3>LBs#hz() zx{9Tp-kDTpa2}Nku3gY*!@stmIH*w);u>6%snO612@T1((nyWrMA90wvQCLK#9DIz zk0!OjfeRv(H;cR5a|Ib)_HeB^7Gi6|KHwm`G$~iDcXw#x>(?k|G}6be8V-;NS`?O@ z6G5M!HqXyH-5(1(Wd2p4l8(56nv2+j7CcWJC&G*Xe=&InKV$qPcGj3gTHV#=PX;G3 zJF1luj!!%5(2fxz^R|(devxk&>)^z$N1VXV(kVjTgvnHbXDv0zqbofHnJ<_^>aQvr zuDTRZU2 z)v(=dzB@_lPHy~20mh$HSD(yT@AXb}pb|QCDA1a{!1R|5bZfu~E{_Kndr*=&zh(&4 z-xlbcJh&hK;&{_3aiJB*ISY_anNV%tUz32|UK+uTQL=uL*vn<&ds z6f$*NI675%#0DF<59?>X^<{zeZGq$>3pwg~E>_CU`?^MvtQ$h6twxkf*Y$HI3nkVB z8#rv0i2ooo6nB=R^bH7M!4DhLF9lzj_i5Q>62ym+=`V_u^1dn3C@ytHIypGS%K(Rq zz)c7!`g;TMgNBgEc)(&K#{)lxDBZ}jmvIODw8K-09*W@3j|h{aBsv2cr#Q5r?;XJ~ zO^`H27WPl{K{Qsf7U>*cW#uz%ahEJXVNCv+OaSm|M{2vMoD_+=IzQ_z zNjGt6>F>q8-s%Jr86$5uJicNL{k!ExcY;AF(`X)>;&2Ek$(uq->28-)WK|TeP=E+g z>hNf_Y)-hsOKzno%+OpVNvQ)Yp(DiRn#6lXCG{FEH;C>RV9UDm9LW%MtqewfG?9E>@EYTaam; zgNlTq%^%Jwm&nRqsLY_;6i3%qKohk=M@Ub_Oji2q2J0taz9T@q01H0)%B}reV_$^B zQy5Q)E-#Z?B1U2BGgDvf!pb;F_RDIPCaBeQY`H{D6s5c-3290)(VOF7JrkBa>G}Hz z`o)?=61H&iBq4O@+B~JqWT@K}Uf#cMDS}s6nenhgQ)4=i2d zE_~Yxu`?pRy1?D|uC|~-ZA9D}cCV4Y3}>FZCojgoHqh9!0bOPhPK>wgjhqE9fF2mz zopX7KlC1zyj6tyoy}1>VvE?{Ro-dk^9_X$KG}iIhMCwc{7VFd-U*W{^-J^MndF2&b zziTVk)LhnvGct&;t4nZ`;_!qm(l@h<^zlp~)(-=0%|BSK?chKxP$u(GNx?69hd9`L zrS2c_?Ste_L%f+FOJaP)1Saz^!K7u|vgGk>OPTF>i0RFlM?5wtRG%&F%xr=)D{!-CLkf+jMz>ZhOCP^ z#0AG&pqDjJB?XpDl_Hy!Afa$^ka5amhY9Kf;E#Jqky~$I4Hgot=hfns;CdJ{V zc(s)*iTJD(@hw@BW!oSRbi`8qfOMpzAB0E}3FG=pu4LA&jL{aSEwR=z3$L3Q>riZw zl5I{QOmu`iY6@&GChbjKoPz7yNY}IL4_=X+Y}^-v<+AbI!+%(F-@&H9w#gyHsx*Wi zf0XIXtRn}nw!rjd{f%zs=cmFd%Y+>nYYDsB;&G^=rEz@2$W2agWXM*b_;!X1V7TcO??#g2US_Uyw-mX2%ucL z_(oA#IlQ1EJ;hq~CRanLPg)(V5Vfei=$ftv&B*eXXF(aQR>n_-?vWQj3Ei&>@o!oD!Hm-o5^w&MQp;o+Oq{jc5n;m%9^ z*C%CUPiPS2mRk)Su)`II`86@-U zQBcg3y|?IXdW(&9G?=HAlw4KEn{YAS853ye{c6n}Reo<(V$sn!dnCe}_?an8qQwbh z28Fbnrkz6_A*1r%-CZN| zIkec2(bau$2;tcB+z|hfsAsTnn<5~~PQ6}Ff9ZN|&-20t3f0pk>|U!hV1`K5CW4+( zN9WG2TD#(mD&|XZ{sf?6ifYc#7Kn|-<9nx`8_5>IjLWlIVXSaY^EZF-`t75a(4;egZ;Fu-qp}({3Dm(8m!LwqD)t*P3>CO_Mb-#MHSdsBZLR1Q zM%B7RW@wsHT8(o;@_a^|%~VvL*%m1FwM;zYMc^Rg7j%BsA0QD(3@-i5JQ_B34(hx0 z+Lr|h<^NaKB*1KG8cHL(i?3m!@wm8RrR)EP0)L5T75>-m?m>F}zq51jvi^UH=ZTl| zeC4iUKvhPOdLZzZ3j0)T2OtaPB#^s!>2yg$JZ}g;Ujf+FD5BPoAgLP&ScM4f6eEgV zN|;0|LQpA)Rk17q0!#@D5AVDn7pH1Hv@{pwmY_ce@7gp9a88jB2mOZ78K9>V*G3TQ zu0k!JdrB1#S|4l>a8V4gtGQ}bL z<`4@1-Z0I|{@|W<>@R*e#s2c;M;kjh#QJYfVE;)!t_Ny>732R+{a`-SYG$aJ=ngTgD|fJom=U4(cV zjy*e-Q3?K(mk}c2By+&4+(JmWOc5vHGB2eF+J2KvZK-kJ^RhX%6|F$5d~i&tJc0SNq~-vRLll-SLai zU%!H@9cRJo<>qxYfy6KzXsc=- zam}8enLmys$0IJtToFY)t`<}VG$J7=88{&qRwuOs_GtAQ2jPlpnPU+nq8wNw2vd!n z=h=OqUcB&ez&D?(wL+9(jrPc(V^rwDPjLM5IISF?v*b#y;GAV0%)}(rwj#1vVk?1d zMF)DEDtjXX{}CDjV%GTm=8CeOZd~D|;e(6ry)|NzbY^g@s$XTq;Eyt-Myfy=`Jy_3 z901WOnH3<+JU)ElR^}s3DnD9BA!`w2@niRqP!fK`&Jocp>l*1Am9$HgRP7LPAtl`* zo3mO^Q1j(K3}zO4`FAALao_%@v&j@tZKeLx{;`a8QTrIoB=ES=|GtG+ys z|E$>mff@B1@Bh@_?CfOYKh|INf1cur{C~@A{R)o1>uv;Xh*;kwoS_aYt4NJzqGaIhT(Aw71PY6~U_W|AWKBbo{USo0swbBu_<;vj6mL;>!MFxyI6}u!ALz zc5(DS9tn`;R9EdPzJmH!C6~EE>vG*YPDRqVrvOx^zB~a`tanZ{$44=z;~1WW@f+{e z4IZ!kpL^8DPmcaKXV{sOsp|#!amrX_{~he5^#9I5{mqO2*ONRO=p3*NTsYO{t&VPH z*hQlS;Zgs6YkXx*F*UUfG?)>JC=bd{5uIVj0RSBC6-3 z0^%(w>KwPJX8Jrxk!BJbTQ#08@P5}aj-}7tx~rd@@!xAVk5Aj?+{-Iw$KCwVr6H@>EQ_c@w&cg(t3H%4!M+muEdnNLkRg9!L0@oFQ>p5mHFZ}8;C z=#4>K?jsH-tp7#BrGt+qjujg8%fbrq7W(e<0A^k*zM-+A5SHFhl2<7s4&7b3E^(GM zs-tNw^?oG+3_2X*fLp{AhYOzrkh&x!mXo}WsUnn$7*-ItDh?GdO=<}(ZED)<*B`t& z_N^&yync=TF6$(~FKXbRgxxW&MYq$$H4<9;ho)_8pi_&;YipWzi^MT`Tb!>0MzeN{ z48t%G|404-?@zs%HJ@8HYK|7RwOryEx|Q?6e+wqiZDi|DNjO`K%&|AG%{^bRkFM-dicwN1bB|!kZM{CQrAG2|2VqdF?ZgWb)$X> zX}Jx`n(QN9qFW>ov5$pYG5d?(Z-C&C+AXvk|F8Tf@fZBkZc*CvgFD2aTXd-pFb#Kh z_x3mROLU6>H5|#)OlB=O-V~x{a&NecjtN~vIG!kUnhP2d7l^b-xHS3J4;vvWkQg|< z5ziG=3PSQIWV_8l=Tln+f9FN8)UZ)vqRVWW%WZTCFx{6X@&a_(=?+iZr)RxiFHI>- zFop86q8#4xLDdyD6{iefFZzkjnu~BI&^viMBn86F6SmGVh2b^x7LJWZ7&&+nO2JFd zazJ-OadD;G=D^F}gXfsaqjFxtcs4AM!uIPlx8QYd@x+1?hs;Sx(ap>X5%KwYk3w*5 zYdpr3M>J4b$>ZOm=(W__Vc6P zewf5$rv?R!Iul+CHW7?Nl+}@v=nZikR%h;yYKwsjF)Cn*P#AHIiKfIIfe92tFW|@i zIqh84?e1pSTBFnk4mK4#@TB4A3 znHy{Q4aM-p^34kgm(&AXYY$SFJ>q=vPLvQ2>oGIWv4w-Q+Nr>}$>Ag~@K63=V0`dp zBjY3vTQq{Ka)i&Y<5Rw>q?nmxP&#qZ2!}TqyT~RJurER+V8vhp2BBJxiB)vh&KD&)Z6ql`B(R`Ud(jRB-o72vA?dA62teS+$rc;hJK$YtX5WyDNGAN7wv z@_INFHf@P3x;~R4prP>v`htdVga86dP&HA?OH=wXP_H1m60J1Fi;l4-$s@|c{gb`xt(pv;dLtk{5HUU%x^bgeY zyb!hG!XGM^Y(Odku?jEi(O_H5M>t^Z9Pl(0_CmNrXrztLVcV0HiuvdZ6Ipp`*Rydf z7th6L3i(+Fx7^$;vP>p0D@1c_xfGE}T1avD%ePd||C`dAdSItIOk}Le@f={5X&ge&z;aS33G>C}O4yhVvCBspaVfDA zK|UxSR8estZLxF#k=tZS!fp6-;rlp%tA7p-Lmcq8j(5Y@iC=>@VqfTTKJuJATdpAX zS6Z9!#fRrkYFQuANUJ)EYGDIf^jIaMD4(0j%pBd46c`)*-c2Z z#eXQ~DEua3p=~dKq*$Yi`$y2LON5^1t68{oD(R65vt^QvMG*B!m3Kxd#lIhTV`24v z+n!lnL~C@=to!UEchy*+>-N_yYbD);$GlQ4su9@WV&!Af>#-^HsL3OQCi}}p@1)Qw z%((3)0>s5Ld3psyT=f2*$6X}g99U1pBxKq*z;lm7dIK-&)XRAk-ASydU@9FkYm`l3 zIB+c|`lKjHB@C3DGK%Gu8Re1jSJ=)E7f_m=V{g1*HX)oiWH%muYBhl)nM}l?hu|L& zv>x;AzZn^gJO_s}ezt&qYBgEGCugnZiHW+=v`r@P39NPpL%o`CK#qR0##ezg8j*M{ z#)iGAegIpBJL*t?$Y5;pvrbX2H4Z#VnQa?QSO{B`BW|C(iX=bM(7#T zy2cWx#&T_e?L>{x6RTH9iI~LsXyqi3AR#7EShtCNIW)qK=T4bj@PzB3RU!BbRXa=N z^Hk~@edVfrAWX(+eTv<2u=D}`l-0(@(N7351ffcpv+*ql)ZH|z>7NX{IS zA-8gR{XNPt=3+e*8JhyjOm-fM{^W(i6FMJSve~jHO*UjyJi@xBH?onc0$A||6qS_H`REN7$H{1YJz!Y zQc=fTJO{43|CpWr1n<0b4c>`sCj@uI1aE zzw#Dg>Cg;PAj*;u7mbz>z?5&5SPR%y46bvG*f`$2QZasym1;C&$fmw8@r^-b6Om zHkk0+2%=$cBT7QNjY97VyW0qk&?zOJ`!}R?GV$kDWI{s#t!N{c&+wsj#jM=Pgjty| z-Cq{X_m@a9doK9`;WFEaa>MneCw)kSWGV#7OmqgDN2~(CrG(QgwDh@`ftcscHz|@G`q*mle2DH;Y@}d z7bm`PnoBs}ZJk{72kqXslwfAi1Hu8vImphNjl;b+e^vOu=$(L74g*T;kN$lTIR9w; zoxS?Uw`5#K#U6Pc`{xn@K&uKsEN5Bk6PGjmZTRV1bEhqCmgn%pKcRl}^uz!ML$+$d zR=5Z-{TsUIpgDFI+X#o_9MUl9KlIe~0q3l6z$)@T`^jon8mmuM3y9=IRm`3pDrbeF z?r@Sg^SE&wwHL2%I)^D*zu>mwz>;NW;hj=iHAjd5N3d>t1h<=

j@?<2CXa)p-5Ulsbu>vL5A53h1IFN$WUQL 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 F2LQZ2nj-)J diff --git a/charts/jans-1.0.0-b8.tgz b/charts/jans-1.0.0-b8.tgz deleted file mode 100644 index 5d6587f5c0568302e8541b9725184d7f5f8639eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62582 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

i&+H0kfSTW>vpjq!`SugY#y@3 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 S_jZqHXKuY)k9DUFUZ0 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

ARX<0NZ%mRgx;9Dh1o=Ou~zqs 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.17.0-0" -annotations: - artifacthub.io/changes: | - - Janssen 1.0 under dev charts - artifacthub.io/containsSecurityUpdates: "true" - artifacthub.io/images: | - - name: auth-server - image: janssenproject/auth-server:1.0.0_dev - - name: auth-server-key-rotation - image: janssenproject/certmanager:1.0.0_dev - - name: client-api - image: janssenproject/client-api:1.0.0_dev - - name: configuration-manager - image: janssenproject/configuration-manager:1.0.0_dev - - name: config-api - image: janssenproject/config-api:1.0.0_dev - - name: fido2 - image: janssenproject/fido2:1.0.0_dev - - name: opendj - image: gluufederation/opendj:5.0.0_dev - - name: persistence - image: janssenproject/persistence-loader:1.0.0_dev - - name: scim - image: janssenproject/scim:1.0.0_dev - artifacthub.io/license: Apache-2.0 - artifacthub.io/prerelease: "true" -apiVersion: v2 -appVersion: "1.0.0-b11" -icon: https://avatars.githubusercontent.com/u/68292770?s=200&v=4 -home: https://github.com/JanssenProject/jans-cloud-native -sources: -- https://jans.io -- https://github.com/JanssenProject/jans-cloud-native -maintainers: -- name: moabu - email: support@gluu.org -description: Janssen Access and Identity Management -name: jans -version: 1.0.0-b11 -dependencies: -- name: config - condition: global.config.enabled - version: 1.0.0-b11 - repository: "" - -- name: config-api - condition: global.config-api.enabled - version: 1.0.0-b11 - repository: "" - -- name: opendj - condition: global.opendj.enabled - version: 1.0.0-b11 - repository: "" - -- name: auth-server - condition: global.auth-server.enabled - version: 1.0.0-b11 - repository: "" - -- name: fido2 - condition: global.fido2.enabled - version: 1.0.0-b11 - repository: "" - -- name: scim - condition: global.scim.enabled - version: 1.0.0-b11 - repository: "" - -- name: nginx-ingress - condition: global.nginx-ingress.enabled - version: 1.0.0-b11 - repository: "" - -- name: auth-server-key-rotation - condition: global.auth-server-key-rotation.enabled - version: 1.0.0-b11 - repository: "" - -- name: client-api - condition: global.client-api.enabled - version: 1.0.0-b11 - repository: "" - -- name: persistence - condition: global.persistence.enabled - version: 1.0.0-b11 - repository: "" - -- name: cn-istio-ingress - condition: global.istio.ingress - version: 1.0.0-b11 - repository: "" diff --git a/charts/jans/charts/auth-server-key-rotation/Chart.yaml b/charts/jans/charts/auth-server-key-rotation/Chart.yaml deleted file mode 100644 index 1bdba083bb0..00000000000 --- a/charts/jans/charts/auth-server-key-rotation/Chart.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v2 -name: auth-server-key-rotation -version: 1.0.0-b11 -kubeVersion: ">=v1.19.0-0" -description: Responsible for regenerating auth-keys per x hours -type: application -keywords: - - Auth keys Rotation -home: https://jans.io -sources: - - https://github.com/JanssenProject/docker-jans-certmanager - - https://github.com/JanssenFederation/cloud-native-edition/tree/master/pyjans/kubernetes/templates/helm/jans/charts/auth-server-key-rotation -maintainers: - - name: Mohammad Abudayyeh - email: support@jans.io - url: https://github.com/moabu -icon: https://jans.io/favicon.ico -appVersion: "1.0.0-b11" \ No newline at end of file diff --git a/charts/jans/charts/auth-server-key-rotation/templates/service.yaml b/charts/jans/charts/auth-server-key-rotation/templates/service.yaml deleted file mode 100644 index a6941085464..00000000000 --- a/charts/jans/charts/auth-server-key-rotation/templates/service.yaml +++ /dev/null @@ -1,18 +0,0 @@ -{{- if .Values.global.istio.enabled }} -# License terms and conditions: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Service -metadata: - name: {{ include "auth-server-key-rotation.fullname" . }} - labels: -{{ include "auth-server-key-rotation.labels" . | indent 6 }} -spec: - ports: - - name: http - port: 80 - targetPort: 8080 - selector: - app: {{ .Release.Name }}-{{ include "auth-server-key-rotation.name" . }} - type: ClusterIP -{{- end }} \ No newline at end of file diff --git a/charts/jans/charts/auth-server-key-rotation/templates/user-custom-secret-envs.yaml b/charts/jans/charts/auth-server-key-rotation/templates/user-custom-secret-envs.yaml deleted file mode 100644 index e4ffc02cdfb..00000000000 --- a/charts/jans/charts/auth-server-key-rotation/templates/user-custom-secret-envs.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{ if .Values.usrEnvs.secret }} -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs -type: Opaque -data: - {{- range $key, $val := .Values.usrEnvs.secret }} - {{ $key }}: {{ $val | b64enc }} - {{- end}} -{{- end}} \ No newline at end of file diff --git a/charts/jans/charts/auth-server/Chart.yaml b/charts/jans/charts/auth-server/Chart.yaml deleted file mode 100644 index 30f1cf50f6b..00000000000 --- a/charts/jans/charts/auth-server/Chart.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v2 -name: auth-server -version: 1.0.0-b11 -kubeVersion: ">=v1.19.0-0" -description: OAuth Authorization Server, the OpenID Connect Provider, the UMA Authorization Server--this is the main Internet facing component of Janssen. It's the service that returns tokens, JWT's and identity assertions. This service must be Internet facing. -type: application -keywords: - - Autherization - - OpenID -home: https://jans.io -sources: - - https://github.com/JanssenProject/jans-auth-server - - https://github.com/JanssenProject/docker-jans-auth-server - - https://github.com/JanssenFederation/cloud-native-edition/tree/master/pyjans/kubernetes/templates/helm/jans/charts/auth-server -maintainers: - - name: Mohammad Abudayyeh - email: support@jans.io - url: https://github.com/moabu -icon: https://jans.io/favicon.ico -appVersion: "1.0.0-b11" diff --git a/charts/jans/charts/auth-server/templates/auth-server-destination-rules.yaml b/charts/jans/charts/auth-server/templates/auth-server-destination-rules.yaml deleted file mode 100644 index 991470c903c..00000000000 --- a/charts/jans/charts/auth-server/templates/auth-server-destination-rules.yaml +++ /dev/null @@ -1,14 +0,0 @@ -{{- if .Values.global.istio.enabled }} -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: networking.istio.io/v1alpha3 -kind: DestinationRule -metadata: - name: {{ .Release.Name }}-auth-server-mtls - namespace: {{.Release.Namespace}} -spec: - host: {{ index .Values "global" "auth-server" "authServerServiceName" }}.{{ .Release.Namespace }}.svc.cluster.local - trafficPolicy: - tls: - mode: ISTIO_MUTUAL -{{- end }} \ No newline at end of file diff --git a/charts/jans/charts/auth-server/templates/service.yml b/charts/jans/charts/auth-server/templates/service.yml deleted file mode 100644 index 26bfc3c2a12..00000000000 --- a/charts/jans/charts/auth-server/templates/service.yml +++ /dev/null @@ -1,19 +0,0 @@ -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Service -metadata: - name: {{ index .Values "global" "auth-server" "authServerServiceName" }} - namespace: {{ .Release.Namespace }} - labels: -{{ include "auth-server.labels" . | indent 4}} -spec: - {{- if .Values.global.alb.ingress }} - type: NodePort - {{- end }} - ports: - - port: {{ .Values.service.port }} - name: {{ .Values.service.name }} - selector: - app: {{ .Release.Name }}-{{ include "auth-server.name" . }} #auth-server - \ No newline at end of file diff --git a/charts/jans/charts/auth-server/templates/user-custom-secret-envs.yaml b/charts/jans/charts/auth-server/templates/user-custom-secret-envs.yaml deleted file mode 100644 index e4ffc02cdfb..00000000000 --- a/charts/jans/charts/auth-server/templates/user-custom-secret-envs.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{ if .Values.usrEnvs.secret }} -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs -type: Opaque -data: - {{- range $key, $val := .Values.usrEnvs.secret }} - {{ $key }}: {{ $val | b64enc }} - {{- end}} -{{- end}} \ No newline at end of file diff --git a/charts/jans/charts/client-api/templates/service.yaml b/charts/jans/charts/client-api/templates/service.yaml deleted file mode 100644 index 6f90bf39cbf..00000000000 --- a/charts/jans/charts/client-api/templates/service.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Service -metadata: - # the name must match the application - name: {{ index .Values "global" "client-api" "clientApiServerServiceName" }} - namespace: {{ .Release.Namespace }} - labels: -{{ include "client-api.labels" . | indent 4 }} -spec: - ports: - - port: 8444 - name: tcp-{{ include "client-api.name" . }}-admin-gui - - port: 8443 - name: tcp-{{ include "client-api.name" . }}-app-connector - selector: - app: {{ .Release.Name }}-{{ include "client-api.name" . }} \ No newline at end of file diff --git a/charts/jans/charts/client-api/templates/user-custom-secret-envs.yaml b/charts/jans/charts/client-api/templates/user-custom-secret-envs.yaml deleted file mode 100644 index e4ffc02cdfb..00000000000 --- a/charts/jans/charts/client-api/templates/user-custom-secret-envs.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{ if .Values.usrEnvs.secret }} -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs -type: Opaque -data: - {{- range $key, $val := .Values.usrEnvs.secret }} - {{ $key }}: {{ $val | b64enc }} - {{- end}} -{{- end}} \ No newline at end of file diff --git a/charts/jans/charts/cn-istio-ingress/Chart.yaml b/charts/jans/charts/cn-istio-ingress/Chart.yaml deleted file mode 100644 index 21ff601208a..00000000000 --- a/charts/jans/charts/cn-istio-ingress/Chart.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v2 -name: cn-istio-ingress -version: 1.0.0-b11 -kubeVersion: ">=v1.19.0-0" -description: Istio Gateway -type: application -keywords: - - istio - - gateway -home: https://jans.io/ -sources: - - https://jans.io/ - - https://github.com/JanssenFederation/cloud-native-edition/tree/master/pyjans/kubernetes/templates/helm/jans/charts/cn-istio-ingress -maintainers: - - name: Mohammad Abudayyeh - email: support@jans.io - url: https://github.com/moabu -icon: https://jans.io/favicon.ico -appVersion: "1.0.0-b11" diff --git a/charts/jans/charts/cn-istio-ingress/README.md b/charts/jans/charts/cn-istio-ingress/README.md deleted file mode 100644 index cf87ee6de29..00000000000 --- a/charts/jans/charts/cn-istio-ingress/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# cn-istio-ingress - -![Version: 1.0.0-b11](https://img.shields.io/badge/Version-1.0.0--b11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-b11](https://img.shields.io/badge/AppVersion-1.0.0--b11-informational?style=flat-square) - -Istio Gateway - -**Homepage:** - -## Maintainers - -| Name | Email | Url | -| ---- | ------ | --- | -| Mohammad Abudayyeh | support@jans.io | https://github.com/moabu | - -## Source Code - -* -* - -## Requirements - -Kubernetes: `>=v1.19.0-0` - ----------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0) diff --git a/charts/jans/charts/config-api/templates/service.yaml b/charts/jans/charts/config-api/templates/service.yaml deleted file mode 100644 index e19d56a8952..00000000000 --- a/charts/jans/charts/config-api/templates/service.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Service -metadata: - # the name must match the application - name: {{ index .Values "global" "config-api" "configApiServerServiceName" }} - namespace: {{ .Release.Namespace }} - labels: -{{ include "config-api.labels" . | indent 4 }} -spec: - ports: - - port: 9444 - name: tcp-{{ include "config-api.name" . }}-ssl - - port: 8074 - name: tcp-{{ include "config-api.name" . }}-http - selector: - app: {{ .Release.Name }}-{{ include "config-api.name" . }} \ No newline at end of file diff --git a/charts/jans/charts/config/Chart.yaml b/charts/jans/charts/config/Chart.yaml deleted file mode 100644 index dd292a839fe..00000000000 --- a/charts/jans/charts/config/Chart.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v2 -name: config -version: 1.0.0-b11 -kubeVersion: ">=v1.19.0-0" -description: Configuration parameters for setup and initial configuration secret and config layers used by Janssen services. -type: application -keywords: - - configuration - - secrets -home: https://jans.io/reference/container-configs/ -sources: - - https://jans.io/reference/container-configs/ - - https://github.com/JanssenProject/docker-jans-configuration-manager - - https://github.com/JanssenFederation/cloud-native-edition/tree/master/pyjans/kubernetes/templates/helm/jans/charts/config -maintainers: - - name: Mohammad Abudayyeh - email: support@jans.io - url: https://github.com/moabu -icon: https://jans.io/favicon.ico -appVersion: "1.0.0-b11" diff --git a/charts/jans/charts/config/templates/secrets.yaml b/charts/jans/charts/config/templates/secrets.yaml deleted file mode 100644 index 055048f6a84..00000000000 --- a/charts/jans/charts/config/templates/secrets.yaml +++ /dev/null @@ -1,122 +0,0 @@ -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "config.fullname" . }}-gen-json-file - namespace: {{ .Release.Namespace }} -type: Opaque -stringData: - generate.json: |- - { - "hostname": {{ .Values.global.fqdn | quote }}, - "country_code": {{ .Values.countryCode | quote }}, - "state": {{ .Values.state | quote }}, - "city": {{ .Values.city | quote }}, - "admin_pw": {{ .Values.adminPassword | quote }}, - "ldap_pw": {{ .Values.ldapPassword | quote }}, - "redis_pw": {{ .Values.redisPassword | quote }}, - "email": {{ .Values.email | quote }}, - "org_name": {{ .Values.orgName | quote }}, - "optional_scopes": [{{if .Values.global.opendj.enabled}}"ldap"{{end}}{{if .Values.global.fido2.enabled}},"fido2"{{end}}{{if .Values.global.scim.enabled}},"scim"{{end}}{{if index .Values "global" "client-api" "enabled"}},"client-api"{{end}}] - } - -{{ if eq .Values.global.cnPersistenceType "sql" }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-sql-pass - namespace: {{ .Release.Namespace }} -type: Opaque -data: - sql_password: {{ .Values.configmap.cnSqldbUserPassword | b64enc }} -{{- end }} - -{{ if or ( eq .Values.global.cnPersistenceType "couchbase" ) ( eq .Values.global.cnPersistenceType "hybrid" ) }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-cb-pass -type: Opaque -data: - couchbase_password: {{ .Values.configmap.cnCouchbasePassword | b64enc }} - -{{- if not .Values.global.istio.enabled }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-cb-crt -type: Opaque -data: - couchbase.crt: {{ .Values.configmap.cnCouchbaseCrt }} -{{- end }} ---- - -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-superuser-cb-pass -type: Opaque -data: - couchbase_superuser_password: {{ .Values.configmap.cnCouchbaseSuperUserPassword | b64enc }} -{{- end }} - -{{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-google-sa -type: Opaque -data: - google-credentials.json: {{ .Values.configmap.cnGoogleSecretManagerServiceAccount }} -{{- end}} - -{{ if .Values.global.cnObExtSigningJwksCrt }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-ob-ext-signing-jwks-crt-key-pin - namespace: {{ .Release.Namespace }} -type: Opaque -data: - ob-ext-signing.crt: {{ .Values.global.cnObExtSigningJwksCrt }} - {{ if .Values.global.cnObExtSigningJwksKey }} - ob-ext-signing.key: {{ .Values.global.cnObExtSigningJwksKey }} - {{- end }} - {{ if .Values.global.cnObExtSigningJwksKeyPassPhrase }} - ob-ext-signing.pin: {{ .Values.global.cnObExtSigningJwksKeyPassPhrase }} - {{- end }} -{{- end }} -{{ if .Values.global.cnObTransportCrt }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-ob-transport-crt-key-pin - namespace: {{ .Release.Namespace }} -type: Opaque -data: - ob-transport.crt: {{ .Values.global.cnObTransportCrt }} - {{ if .Values.global.cnObTransportKey }} - ob-transport.key: {{ .Values.global.cnObTransportKey }} - {{- end }} - {{ if .Values.global.cnObTransportKeyPassPhrase }} - ob-transport.pin: {{ .Values.global.cnObTransportKeyPassPhrase }} - {{- end }} -{{- end }} -{{ if .Values.global.cnObTransportTrustStore }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-ob-transport-truststore - namespace: {{ .Release.Namespace }} -type: Opaque -data: - ob-transport-truststore.p12: {{ .Values.global.cnObTransportTrustStore }} -{{- end }} \ No newline at end of file diff --git a/charts/jans/charts/config/templates/user-custom-envs.yaml b/charts/jans/charts/config/templates/user-custom-envs.yaml deleted file mode 100644 index 8cd9b482b1e..00000000000 --- a/charts/jans/charts/config/templates/user-custom-envs.yaml +++ /dev/null @@ -1,36 +0,0 @@ -{{ if .Values.global.usrEnvs.secret }} -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-global-user-custom-envs -type: Opaque -data: - {{- range $key, $val := .Values.global.usrEnvs.secret }} - {{ $key }}: {{ $val | b64enc }} - {{- end}} -{{- end}} -{{ if .Values.global.usrEnvs.normal }} ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-global-user-custom-envs -data: - {{- range $key, $val := .Values.global.usrEnvs.normal }} - {{ $key }}: {{ $val }} - {{- end}} -{{- end}} -{{ if .Values.usrEnvs.secret }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs -type: Opaque -data: - {{- range $key, $val := .Values.usrEnvs.secret }} - {{ $key }}: {{ $val | b64enc }} - {{- end}} -{{- end}} diff --git a/charts/jans/charts/fido2/templates/service.yml b/charts/jans/charts/fido2/templates/service.yml deleted file mode 100644 index 34777b9c4a6..00000000000 --- a/charts/jans/charts/fido2/templates/service.yml +++ /dev/null @@ -1,19 +0,0 @@ -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Service -metadata: - name: {{ .Values.global.fido2.fido2ServiceName }} - namespace: {{ .Release.Namespace }} - labels: -{{ include "fido2.labels" . | indent 4}} -spec: - {{- if .Values.global.alb.ingress }} - type: NodePort - {{- end }} - ports: - - port: {{ .Values.service.port }} - name: {{ .Values.service.name }} - selector: - app: {{ .Release.Name }}-{{ include "fido2.name" . }} #fido2 - \ No newline at end of file diff --git a/charts/jans/charts/fido2/templates/user-custom-secret-envs.yaml b/charts/jans/charts/fido2/templates/user-custom-secret-envs.yaml deleted file mode 100644 index e4ffc02cdfb..00000000000 --- a/charts/jans/charts/fido2/templates/user-custom-secret-envs.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{ if .Values.usrEnvs.secret }} -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs -type: Opaque -data: - {{- range $key, $val := .Values.usrEnvs.secret }} - {{ $key }}: {{ $val | b64enc }} - {{- end}} -{{- end}} \ No newline at end of file diff --git a/charts/jans/charts/opendj/templates/configmaps.yaml b/charts/jans/charts/opendj/templates/configmaps.yaml deleted file mode 100644 index 61585b29498..00000000000 --- a/charts/jans/charts/opendj/templates/configmaps.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.multiCluster.enabled }} -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-serf-peers - namespace: {{ .Release.Namespace }} -data: - serf-peers-static.json: | - {{ .Values.multiCluster.serfPeers | toJson }} -{{- end }} diff --git a/charts/jans/charts/opendj/templates/secrets.yaml b/charts/jans/charts/opendj/templates/secrets.yaml deleted file mode 100644 index 71bbf02eedb..00000000000 --- a/charts/jans/charts/opendj/templates/secrets.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -{{- if .Values.multiCluster.enabled }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-serf-key -type: Opaque -data: - serf-key: {{ .Values.multiCluster.serfKey | b64enc }} -{{- end }} \ No newline at end of file diff --git a/charts/jans/charts/opendj/templates/user-custom-secret-envs.yaml b/charts/jans/charts/opendj/templates/user-custom-secret-envs.yaml deleted file mode 100644 index e4ffc02cdfb..00000000000 --- a/charts/jans/charts/opendj/templates/user-custom-secret-envs.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{ if .Values.usrEnvs.secret }} -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs -type: Opaque -data: - {{- range $key, $val := .Values.usrEnvs.secret }} - {{ $key }}: {{ $val | b64enc }} - {{- end}} -{{- end}} \ No newline at end of file diff --git a/charts/jans/charts/persistence/Chart.yaml b/charts/jans/charts/persistence/Chart.yaml deleted file mode 100644 index efee1222d44..00000000000 --- a/charts/jans/charts/persistence/Chart.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v2 -name: persistence -version: 1.0.0-b11 -kubeVersion: ">=v1.19.0-0" -description: Job to generate data and initial config for Janssen Server persistence layer. -type: application -keywords: - - persistence prep -home: https://jans.io -sources: - - https://github.com/JanssenProject/docker-jans-persistence-loader - - https://github.com/JanssenFederation/cloud-native-edition/tree/master/pyjans/kubernetes/templates/helm/jans/charts/persistence -maintainers: - - name: Mohammad Abudayyeh - email: support@jans.io - url: https://github.com/moabu -icon: https://jans.io/favicon.ico -appVersion: "1.0.0-b11" - diff --git a/charts/jans/charts/persistence/templates/service.yaml b/charts/jans/charts/persistence/templates/service.yaml deleted file mode 100644 index b75b9b765c2..00000000000 --- a/charts/jans/charts/persistence/templates/service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -{{- if .Values.global.istio.enabled }} -# License terms and conditions: -# https://www.apache.org/licenses/LICENSE-2.0 -# Used with Istio -apiVersion: v1 -kind: Service -metadata: - name: {{ include "persistence.fullname" . }} - labels: -{{ include "persistence.labels" . | indent 6 }} -spec: - ports: - - name: http - port: 80 - targetPort: 8080 - selector: - app: {{ .Release.Name }}-{{ include "persistence.name" . }} - type: ClusterIP -{{- end }} \ No newline at end of file diff --git a/charts/jans/charts/persistence/templates/user-custom-secret-envs.yaml b/charts/jans/charts/persistence/templates/user-custom-secret-envs.yaml deleted file mode 100644 index e4ffc02cdfb..00000000000 --- a/charts/jans/charts/persistence/templates/user-custom-secret-envs.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{ if .Values.usrEnvs.secret }} -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs -type: Opaque -data: - {{- range $key, $val := .Values.usrEnvs.secret }} - {{ $key }}: {{ $val | b64enc }} - {{- end}} -{{- end}} \ No newline at end of file diff --git a/charts/jans/charts/scim/Chart.yaml b/charts/jans/charts/scim/Chart.yaml deleted file mode 100644 index bed1eac041d..00000000000 --- a/charts/jans/charts/scim/Chart.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v2 -name: scim -version: 1.0.0-b11 -kubeVersion: ">=v1.19.0-0" -description: System for Cross-domain Identity Management (SCIM) version 2.0 -type: application -keywords: - - SCIM - - API -home: https://jans.io -sources: - - https://github.com/JanssenProject/jans-scim - - https://jans.io/api-guide/scim-api/ - - https://github.com/JanssenProject/docker-jans-scim - - https://github.com/JanssenFederation/cloud-native-edition/tree/master/pyjans/kubernetes/templates/helm/jans/charts/scim -maintainers: - - name: Mohammad Abudayyeh - email: support@jans.io - url: https://github.com/moabu -icon: https://jans.io/favicon.ico -appVersion: "1.0.0-b11" diff --git a/charts/jans/charts/scim/templates/service.yml b/charts/jans/charts/scim/templates/service.yml deleted file mode 100644 index a16e9abc68c..00000000000 --- a/charts/jans/charts/scim/templates/service.yml +++ /dev/null @@ -1,19 +0,0 @@ -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Service -metadata: - name: {{ .Values.global.scim.scimServiceName }} - namespace: {{ .Release.Namespace }} - labels: -{{ include "scim.labels" . | indent 4}} -spec: - {{- if .Values.global.alb.ingress }} - type: NodePort - {{- end }} - ports: - - port: {{ .Values.service.port }} - name: {{ .Values.service.name }} - selector: - app: {{ .Release.Name }}-{{ include "scim.name" . }} #scim - \ No newline at end of file diff --git a/charts/jans/charts/scim/templates/user-custom-secret-envs.yaml b/charts/jans/charts/scim/templates/user-custom-secret-envs.yaml deleted file mode 100644 index e4ffc02cdfb..00000000000 --- a/charts/jans/charts/scim/templates/user-custom-secret-envs.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{ if .Values.usrEnvs.secret }} -# License terms and conditions for Janssen Cloud Native Edition: -# https://www.apache.org/licenses/LICENSE-2.0 -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs -type: Opaque -data: - {{- range $key, $val := .Values.usrEnvs.secret }} - {{ $key }}: {{ $val | b64enc }} - {{- end}} -{{- end}} \ No newline at end of file diff --git a/helm/.gitignore b/helm/.gitignore new file mode 100644 index 00000000000..0f0d1e6618f --- /dev/null +++ b/helm/.gitignore @@ -0,0 +1,114 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# PyCharm project settings +.idea +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +*.pyz +/pygluu/kubernetes/kubernetes-client +.DS_Store + +docs/_build +setup.log +docs/setup.log + +# generated by guizipapp builder +include diff --git a/helm/LICENSE b/helm/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/helm/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/helm/MANIFEST.in b/helm/MANIFEST.in new file mode 100644 index 00000000000..d59211e34cd --- /dev/null +++ b/helm/MANIFEST.in @@ -0,0 +1,3 @@ +recursive-include pygluu/kubernetes/templates * +recursive-include pygluu/kubernetes/gui/templates * +recursive-include pygluu/kubernetes/gui/static * \ No newline at end of file diff --git a/helm/Makefile b/helm/Makefile new file mode 100644 index 00000000000..2a2378fc98a --- /dev/null +++ b/helm/Makefile @@ -0,0 +1,16 @@ +.DEFAULT_GOAL := develop + +develop: + /usr/bin/env python3 setup.py develop + +install: + pip3 install . + +uninstall: + pip3 uninstall pygluu-kubernetes -y + +zipapp: + shiv --compressed -o pygluu-kubernetes.pyz -p '/usr/bin/env python3' -e pygluu.kubernetes.create:main . --no-cache + +guizipapp: + shiv --compressed -o pygluu-kubernetes-gui.pyz -p '/usr/bin/env python3' -e pygluu.kubernetes.gui.server:run . --no-cache diff --git a/helm/README.md b/helm/README.md new file mode 100644 index 00000000000..03b20ac20df --- /dev/null +++ b/helm/README.md @@ -0,0 +1,54 @@ +![microk8s](https://github.com/GluuFederation/cloud-native-edition/workflows/microk8s/badge.svg?branch=5.0) +![minikube](https://github.com/GluuFederation/cloud-native-edition/workflows/minikube/badge.svg?branch=5.0) +![awseks](https://github.com/GluuFederation/cloud-native-edition/workflows/awseks/badge.svg?branch=5.0) +![googlegke](https://github.com/GluuFederation/cloud-native-edition/workflows/googlegke/badge.svg?branch=5.0) +![testcases](https://github.com/GluuFederation/cloud-native-edition/workflows/testcases/badge.svg?branch=5.0) +[![codecov](https://codecov.io/gh/GluuFederation/cloud-native-edition/branch/master/graph/badge.svg)](https://codecov.io/gh/GluuFederation/cloud-native-edition) +[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/gluu)](https://artifacthub.io/packages/search?repo=gluu) +# pygluu-kubernetes + +## Kubernetes recipes + +- Install [Gluu](https://gluu.org/docs/gluu-server/latest/installation-guide/install-kubernetes/) + +## Build `pygluu-kubernetes.pyz` manually + +## Prerequisites + +1. Python 3.6+. +1. Python `pip3` package. + +## Installation + +### Standard Python package + +1. Create virtual environment and activate: + + ```sh + python3 -m venv .venv + source .venv/bin/activate + ``` + +1. Install the package: + + ``` + make install + ``` + + This command will install executable called `pygluu-kubernetes` and `pygluu-kubernetes-gui` available in virtual environment `PATH`. + +### Python zipapp + +1. Install [shiv](https://shiv.readthedocs.io/) using `pip3`: + + ```sh + pip3 install shiv + ``` + +1. Install the package: + + ```sh + make zipapp + ``` + + This command will generate executable called `pygluu-kubernetes.pyz` under the same directory. \ No newline at end of file diff --git a/helm/docs/Makefile b/helm/docs/Makefile new file mode 100644 index 00000000000..d4bb2cbb9ed --- /dev/null +++ b/helm/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/helm/docs/README.md b/helm/docs/README.md new file mode 100644 index 00000000000..946c67a2fc2 --- /dev/null +++ b/helm/docs/README.md @@ -0,0 +1,9 @@ +Building Documentation +====================== + +To build the documentation: + + pip3 install sphinx sphinx-autobuild + sphinx-autobuild . _build/html/ + +Visit `http://localhost:8000` in the browser. diff --git a/helm/docs/conf.py b/helm/docs/conf.py new file mode 100644 index 00000000000..ecde154ddc1 --- /dev/null +++ b/helm/docs/conf.py @@ -0,0 +1,77 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import re +import sys +sys.path.insert(0, os.path.abspath('../')) + + +def find_version(*file_paths): + here = os.path.abspath(os.path.dirname(__file__)) + with open(os.path.join(here, *file_paths), 'r') as f: + version_file = f.read() + version_match = re.search( + r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M, + ) + if version_match: + return version_match.group(1) + raise RuntimeError("Unable to find version string.") + + +# -- Project information ----------------------------------------------------- + +project = 'pygluu.kubernetes' +copyright = '2020, Gluu' +author = 'Gluu' + +# The full version, including alpha/beta/rc tags +release = find_version("../pygluu/kubernetes/__init__.py") + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", +] + +autodoc_mock_imports = [ + "click", + "ruamel", + "kubernetes", + "OpenSSL", + "cryptography", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'nature' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/helm/docs/helm.rst b/helm/docs/helm.rst new file mode 100644 index 00000000000..4a50adc6837 --- /dev/null +++ b/helm/docs/helm.rst @@ -0,0 +1,11 @@ +Helm +~~~~ + +.. module:: pygluu.kubernetes.helm + +.. autofunction:: register_op_client + +.. autoclass:: pygluu.kubernetes.helm.Helm + :members: + :private-members: + :undoc-members: diff --git a/helm/docs/helpers.rst b/helm/docs/helpers.rst new file mode 100644 index 00000000000..6e3f27aa200 --- /dev/null +++ b/helm/docs/helpers.rst @@ -0,0 +1,26 @@ +Helpers +~~~~~~~ + +.. module:: pygluu.kubernetes.helpers + +.. autofunction:: update_settings_json_file + +.. autofunction:: exec_cmd + +.. autofunction:: get_logger + +.. autofunction:: ssh_and_remove + +.. autofunction:: check_port + +.. autofunction:: copy + +.. autofunction:: copy_templates + +.. autofunction:: check_microk8s_kube_config_file + +.. autofunction:: get_supported_versions + +.. autofunction:: generate_password + +.. autofunction:: prompt_password diff --git a/helm/docs/index.rst b/helm/docs/index.rst new file mode 100644 index 00000000000..bbb8a4f99b1 --- /dev/null +++ b/helm/docs/index.rst @@ -0,0 +1,31 @@ +.. pygluu.kubernetes documentation master file, created by + sphinx-quickstart on Sat Sep 5 01:18:38 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +pygluu.kubernetes +~~~~~~~~~~~~~~~~~ + +.. toctree:: + :maxdepth: 2 + +pygluu-kubernetes contains classes and functions to deploy Gluu Cloud Native Edition. + +This documentation focuses on guide for developer. For user's guide, head over to https://gluu.org/docs/gluu-server/. + +API Reference +============= + +This part of the documentation lists the API reference of public classes and functions. + +.. toctree:: + :maxdepth: 2 + + helm + helpers + kubeapi + kustomize + pycert + settings + terminal + yamlparser diff --git a/helm/docs/kubeapi.rst b/helm/docs/kubeapi.rst new file mode 100644 index 00000000000..0151cf1df70 --- /dev/null +++ b/helm/docs/kubeapi.rst @@ -0,0 +1,11 @@ +Kubeapi +~~~~~~~ + +.. module:: pygluu.kubernetes.kubeapi + +.. autofunction:: load_kubernetes_config + +.. autoclass:: Kubernetes + :members: + :private-members: + :undoc-members: diff --git a/helm/docs/kustomize.rst b/helm/docs/kustomize.rst new file mode 100644 index 00000000000..a45264f4fb9 --- /dev/null +++ b/helm/docs/kustomize.rst @@ -0,0 +1,11 @@ +Kustomize +~~~~~~~~~ + +.. module:: pygluu.kubernetes.kustomize + +.. autofunction:: register_op_client + +.. autoclass:: Kustomize + :members: + :private-members: + :undoc-members: diff --git a/helm/docs/make.bat b/helm/docs/make.bat new file mode 100644 index 00000000000..922152e96a0 --- /dev/null +++ b/helm/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/helm/docs/pycert.rst b/helm/docs/pycert.rst new file mode 100644 index 00000000000..71c178087dd --- /dev/null +++ b/helm/docs/pycert.rst @@ -0,0 +1,7 @@ +Certificate +~~~~~~~~~~~ + +.. module:: pygluu.kubernetes.pycert + +.. autofunction:: setup_crts +.. autofunction:: check_cert_with_private_key diff --git a/helm/docs/settings.rst b/helm/docs/settings.rst new file mode 100644 index 00000000000..41303972829 --- /dev/null +++ b/helm/docs/settings.rst @@ -0,0 +1,11 @@ +Settings +~~~~~~~~ + +.. module:: pygluu.kubernetes.settings + +.. autofunction:: unlink_values_yaml + +.. autoclass:: SettingsHandler + :members: + :private-members: + :undoc-members: diff --git a/helm/docs/terminal.rst b/helm/docs/terminal.rst new file mode 100644 index 00000000000..471a0f29fb9 --- /dev/null +++ b/helm/docs/terminal.rst @@ -0,0 +1,130 @@ +Terminal +~~~~~~~~ + +The ``pygluu.kubernetes.terminal`` sub-package contains functions and classes +to handle interaction with user-inputs. + +.. module:: pygluu.kubernetes.terminal.helpers + + +.. autofunction:: gather_ip + +.. autoclass:: pygluu.kubernetes.terminal.architecture.PromptArch + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.aws.PromptAws + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.backup.PromptBackup + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.cache.PromptCache + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.configuration.PromptConfiguration + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.confirmsettings.PromptConfirmSettings + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.couchbase.PromptCouchbase + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.gke.PromptGke + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.helm.PromptHelm + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.istio.PromptIstio + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.jackrabbit.PromptJackrabbit + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.ldap.PromptLdap + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.license.PromptLicense + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.namespace.PromptNamespace + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.optionalservices.PromptOptionalServices + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.persistencebackend.PromptPersistenceBackend + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.postgres.PromptPostgres + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.prompt.Prompt + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.redis.PromptRedis + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.replicas.PromptReplicas + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.testenv.PromptTestEnvironment + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.upgrade.PromptUpgrade + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.version.PromptVersion + :members: + :private-members: + :undoc-members: + +.. autoclass:: pygluu.kubernetes.terminal.volumes.PromptVolumes + :members: + :private-members: + :undoc-members: diff --git a/helm/docs/yamlparser.rst b/helm/docs/yamlparser.rst new file mode 100644 index 00000000000..c6327da5c3d --- /dev/null +++ b/helm/docs/yamlparser.rst @@ -0,0 +1,9 @@ +YAML Parser +~~~~~~~~~~~ + +.. module:: pygluu.kubernetes.yamlparser + +.. autoclass:: Parser + :members: + :private-members: + :undoc-members: diff --git a/helm/pygluu/__init__.py b/helm/pygluu/__init__.py new file mode 100644 index 00000000000..4a17056f584 --- /dev/null +++ b/helm/pygluu/__init__.py @@ -0,0 +1,6 @@ +""" + License terms and conditions for Gluu Cloud Native Edition: + https://www.apache.org/licenses/LICENSE-2.0 +""" + +__path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/helm/pygluu/kubernetes/__init__.py b/helm/pygluu/kubernetes/__init__.py new file mode 100644 index 00000000000..1b272b031de --- /dev/null +++ b/helm/pygluu/kubernetes/__init__.py @@ -0,0 +1,7 @@ +""" + License terms and conditions for Gluu Cloud Native Edition: + https://www.apache.org/licenses/LICENSE-2.0 +""" + +__version__ = "1.2.16" +__previous_version__ = "1.2.15" diff --git a/helm/pygluu/kubernetes/couchbase.py b/helm/pygluu/kubernetes/couchbase.py new file mode 100644 index 00000000000..1034eeb2e1f --- /dev/null +++ b/helm/pygluu/kubernetes/couchbase.py @@ -0,0 +1,704 @@ +""" + License terms and conditions for Gluu Cloud Native Edition: + https://www.apache.org/licenses/LICENSE-2.0 + Installs and configures Couchbase +""" + +from pathlib import Path +import shutil +import tarfile +from pygluu.kubernetes.kubeapi import Kubernetes +from pygluu.kubernetes.yamlparser import Parser +from pygluu.kubernetes.helpers import get_logger, exec_cmd +from pygluu.kubernetes.settings import ValuesHandler +from pygluu.kubernetes.pycert import setup_crts +import sys +import base64 +import random +import os + +logger = get_logger("gluu-couchbase ") + + +def extract_couchbase_tar(tar_file): + """ + Extracts couchbase kubernetes tar file + :param tar_file: + """ + extract_folder = Path("./couchbase-source-folder") + logger.info("Extracting {} in {} ".format(tar_file, extract_folder)) + tr = tarfile.open(tar_file) + tr.extractall(path=extract_folder) + tr.close() + + +def set_memory_for_buckets(memory_quota, couchbase_bucket_prefix): + def parse_couchbase_buckets(file, bucket_type, allbuckets): + for bucket in allbuckets: + metadata_name = "gluu" + if bucket: + metadata_name = "gluu-" + bucket + parser = Parser(file, bucket_type, metadata_name) + parser["spec"]["memoryQuota"] = str(memory_quota + 100) + "Mi" + parser["spec"]["name"] = couchbase_bucket_prefix + parser["metadata"]["name"] = couchbase_bucket_prefix + if bucket: + parser["spec"]["name"] = couchbase_bucket_prefix + "_" + bucket + parser["metadata"]["name"] = couchbase_bucket_prefix + "-" + bucket + parser.dump_it() + + buckets = ["", "site", "user"] + ephemeral_buckets = ["cache", "token", "session"] + parse_couchbase_buckets("./couchbase/couchbase-buckets.yaml", + "CouchbaseBucket", buckets) + parse_couchbase_buckets("./couchbase/couchbase-ephemeral-buckets.yaml", + "CouchbaseEphemeralBucket", ephemeral_buckets) + + +def create_server_spec_per_cb_service(zones, number_of_cb_service_nodes, cb_service_name, mem_req, mem_limit, + cpu_req, cpu_limit): + """ + Creates the server spec section inside couchbase.yaml for each couchbase service + :param zones: + :param number_of_cb_service_nodes: + :param cb_service_name: + :param mem_req: + :param mem_limit: + :param cpu_req: + :param cpu_limit: + :return: + """ + server_spec = [] + zones = zones + number_of_zones = len(zones) + size = dict() + # Create defualt size 1 for all the zones available + for n in range(number_of_cb_service_nodes): + random_zone_index = random.randint(0, number_of_zones - 1) + try: + size[zones[random_zone_index]] = size[zones[random_zone_index]] + 1 + except KeyError: + size[zones[random_zone_index]] = 1 + + for k, v in size.items(): + node_zone = k + name = "pvc-" + cb_service_name + if cb_service_name == "analytics": + name = ["pvc-" + cb_service_name] + spec = {"name": cb_service_name + "-" + node_zone, "size": v, "serverGroups": [node_zone], + "services": [cb_service_name], + "resources": {"limits": {"cpu": str(cpu_limit) + "m", "memory": str(mem_limit) + "Mi"}, + "requests": {"cpu": str(cpu_req) + "m", "memory": str(mem_req) + "Mi"}}, + "volumeMounts": {"default": "pvc-general", cb_service_name: name} + } + server_spec.append(spec) + + return server_spec + + +class Couchbase(object): + def __init__(self): + self.settings = ValuesHandler() + self.kubernetes = Kubernetes() + self.storage_class_file = Path("./couchbase/storageclasses.yaml") + self.couchbase_cluster_file = Path("./couchbase/couchbase-cluster.yaml") + self.couchbase_buckets_file = Path("./couchbase/couchbase-buckets.yaml") + self.couchbase_group_file = Path("./couchbase/couchbase-group.yaml") + self.couchbase_user_file = Path("./couchbase/couchbase-user.yaml") + self.couchbase_rolebinding_file = Path("./couchbase/couchbase-rolebinding.yaml") + self.couchbase_ephemeral_buckets_file = Path("./couchbase/couchbase-ephemeral-buckets.yaml") + self.couchbase_source_folder_pattern, self.couchbase_source_file = self.get_couchbase_files + self.couchbase_custom_resource_definition_file = self.couchbase_source_file.joinpath("crd.yaml") + self.couchbase_operator_dac_file = self.couchbase_source_file.joinpath("operator_dac.yaml") + self.couchbase_admission_file = self.couchbase_source_file.joinpath("admission.yaml") + self.couchbase_operator_backup_file = self.couchbase_source_file.joinpath("operator_dac_backup.yaml") + self.filename = "" + # @TODO: Remove flag after depreciation of couchbase operator 2.0 + self.old_couchbase = False + + @property + def get_couchbase_files(self): + """ + Returns the couchbase extracted package folder path containing manifests and the tar package file + :return: + """ + if self.settings.get("installer-settings.couchbase.install"): + couchbase_tar_pattern = "couchbase-autonomous-operator-kubernetes_*.tar.gz" + directory = Path('.') + try: + couchbase_tar_file = list(directory.glob(couchbase_tar_pattern))[0] + if "_1." in str(couchbase_tar_file.resolve()): + logger.fatal("Couchbase Autonomous Operator version must be > 2.0") + sys.exit() + # @TODO: Remove condition and underlying lines after depreciation of couchbase operator 2.0 + if "_2.0" in str(couchbase_tar_file.resolve()): + logger.warning("An newer version of the couchbase operator exists. " + "Please consider canceling out and using it.https://www.couchbase.com/downloads") + self.old_couchbase = True + + except IndexError: + logger.fatal("Couchbase package not found.") + logger.info("Please download the couchbase kubernetes package and place it inside the same directory " + "containing the pygluu-kubernetes.pyz script.https://www.couchbase.com/downloads") + sys.exit() + extract_couchbase_tar(couchbase_tar_file) + couchbase_source_folder_pattern = "./couchbase-source-folder/couchbase-autonomous-operator-kubernetes_*" + couchbase_source_folder = list(directory.glob(couchbase_source_folder_pattern))[0] + + return couchbase_tar_file, couchbase_source_folder + # Couchbase is installed. + return Path("."), Path(".") + + def create_couchbase_gluu_cert_pass_secrets(self, encoded_ca_crt_string, encoded_cb_pass_string, + encoded_cb_super_pass_string): + """ + Create cor patch secret containing couchbase certificate authority crt and couchbase admin password + :param encoded_ca_crt_string: + :param encoded_cb_pass_string: + :param encoded_cb_super_pass_string: + """ + # Remove this if its not needed + self.kubernetes.patch_or_create_namespaced_secret(name="cb-crt", + namespace=self.settings.get("installer-settings.namespace"), + literal="couchbase.crt", + value_of_literal=encoded_ca_crt_string) + + # Remove this if its not needed + self.kubernetes.patch_or_create_namespaced_secret(name="cb-pass", + namespace=self.settings.get("installer-settings.namespace"), + literal="couchbase_password", + value_of_literal=encoded_cb_pass_string) + + self.kubernetes.patch_or_create_namespaced_secret(name="cb-super-pass", + namespace=self.settings.get("installer-settings.namespace"), + literal="couchbase_superuser_password", + value_of_literal=encoded_cb_super_pass_string) + + def setup_backup_couchbase(self): + """ + Setups Couchbase backup strategy + """ + couchbase_backup_file = Path("./couchbase/backup/couchbase-backup.yaml") + parser = Parser(couchbase_backup_file, "CouchbaseBackup") + parser["spec"]["full"]["schedule"] = self.settings.get("installer-settings.couchbase.backup.fullSchedule") + parser["spec"]["incremental"]["schedule"] = self.settings.get( + "installer-settings.couchbase.backup.incrementalSchedule") + parser["spec"]["backupRetention"] = self.settings.get("installer-settings.couchbase.backup.retentionTime") + parser["spec"]["size"] = self.settings.get("installer-settings.couchbase.backup.storageSize") + parser.dump_it() + self.kubernetes.create_namespaced_custom_object(filepath=couchbase_backup_file, + group="couchbase.com", + version="v2", + plural="couchbasebackups", + namespace=self.settings.get("installer-settings.couchbase.namespace")) + + @property + def calculate_couchbase_resources(self): + """ + Return a dictionary containing couchbase resource information calculated + Alpha + @todo: switch to preset values based on ranges for TPS and amount of users + :return: + """ + tps = int(self.settings.get("CN_EXPECTED_TRANSACTIONS_PER_SEC")) + number_of_data_nodes = 0 + number_of_query_nodes = 0 + number_of_index_nodes = 0 + number_of_eventing_service_memory_nodes = 0 + user_ratio = int(self.settings.get("CN_NUMBER_OF_EXPECTED_USERS")) / 50000000 + tps_ratio = tps / 14000 + + if self.settings.get("CN_USING_RESOURCE_OWNER_PASSWORD_CRED_GRANT_FLOW") == "Y": + number_of_data_nodes += tps_ratio * 7 * user_ratio + number_of_query_nodes += tps_ratio * 5 * user_ratio + number_of_index_nodes += tps_ratio * 5 * user_ratio + number_of_eventing_service_memory_nodes += tps_ratio * 4 * user_ratio + + if self.settings.get("CN_USING_CODE_FLOW") == "Y": + number_of_data_nodes += tps_ratio * 14 * user_ratio + number_of_query_nodes += tps_ratio * 12 * user_ratio + number_of_index_nodes += tps_ratio * 13 * user_ratio + number_of_eventing_service_memory_nodes += tps_ratio * 7 * user_ratio + + if self.settings.get("CN_USING_SCIM_FLOW") == "Y": + number_of_data_nodes += tps_ratio * 7 * user_ratio + number_of_query_nodes += tps_ratio * 5 * user_ratio + number_of_index_nodes += tps_ratio * 5 * user_ratio + number_of_eventing_service_memory_nodes += tps_ratio * 4 * user_ratio + + if not self.settings.get("CN_COUCHBASE_GENERAL_STORAGE"): + self.settings.set("CN_COUCHBASE_GENERAL_STORAGE", str(int((tps_ratio * ( + int(self.settings.get("CN_NUMBER_OF_EXPECTED_USERS")) / 125000)) + 5)) + "Gi") + if not self.settings.get("CN_COUCHBASE_DATA_STORAGE"): + self.settings.set("CN_COUCHBASE_DATA_STORAGE", str(int((tps_ratio * ( + int(self.settings.get("CN_NUMBER_OF_EXPECTED_USERS")) / 100000)) + 5)) + "Gi") + if not self.settings.get("CN_COUCHBASE_INDEX_STORAGE"): + self.settings.set("CN_COUCHBASE_INDEX_STORAGE", str(int((tps_ratio * ( + int(self.settings.get("CN_NUMBER_OF_EXPECTED_USERS")) / 200000)) + 5)) + "Gi") + if not self.settings.get("CN_COUCHBASE_QUERY_STORAGE"): + self.settings.set("CN_COUCHBASE_QUERY_STORAGE", str(int((tps_ratio * ( + int(self.settings.get("CN_NUMBER_OF_EXPECTED_USERS")) / 200000)) + 5)) + "Gi") + if not self.settings.get("CN_COUCHBASE_ANALYTICS_STORAGE"): + self.settings.set("CN_COUCHBASE_ANALYTICS_STORAGE", str(int((tps_ratio * ( + int(self.settings.get("CN_NUMBER_OF_EXPECTED_USERS")) / 250000)) + 5)) + "Gi") + + if self.settings.get("CN_COUCHBASE_DATA_NODES"): + number_of_data_nodes = self.settings.get("CN_COUCHBASE_DATA_NODES") + if self.settings.get("CN_COUCHBASE_QUERY_NODES"): + number_of_query_nodes = self.settings.get("CN_COUCHBASE_QUERY_NODES") + if self.settings.get("CN_COUCHBASE_INDEX_NODES"): + number_of_index_nodes = self.settings.get("CN_COUCHBASE_INDEX_NODES") + if self.settings.get("CN_COUCHBASE_SEARCH_EVENTING_ANALYTICS_NODES"): + number_of_eventing_service_memory_nodes = self.settings.get("CN_COUCHBASE_SEARCH_EVENTING_ANALYTICS_NODES") + + data_service_memory_quota = (tps_ratio * 40000 * user_ratio) + 512 + data_memory_request = data_service_memory_quota / 4 + data_memory_limit = data_memory_request + data_cpu_request = data_service_memory_quota / 4 + data_cpu_limit = data_cpu_request + + query_memory_request = data_memory_request + query_memory_limit = query_memory_request + query_cpu_request = data_service_memory_quota / 4 + query_cpu_limit = query_cpu_request + + index_service_memory_quota = (tps_ratio * 25000 * user_ratio) + 256 + index_memory_request = index_service_memory_quota / 3 + index_memory_limit = index_memory_request + index_cpu_request = index_service_memory_quota / 3 + index_cpu_limit = index_cpu_request + + search_service_memory_quota = (tps_ratio * 4000 * user_ratio) + 256 + eventing_service_memory_quota = (tps_ratio * 4000 * user_ratio) + 256 + analytics_service_memory_quota = (tps_ratio * 4000 * user_ratio) + 1024 + + search_eventing_analytics_memory_quota_sum = (search_service_memory_quota + eventing_service_memory_quota + + analytics_service_memory_quota) + search_eventing_analytics_memory_request = tps_ratio * 10000 * user_ratio + search_eventing_analytics_memory_limit = search_eventing_analytics_memory_request + search_eventing_analytics_cpu_request = tps_ratio * 6000 * user_ratio + search_eventing_analytics_cpu_limit = search_eventing_analytics_cpu_request + + # Two services because query is assumed to take the same amount of mem quota + total_mem_resources = \ + data_service_memory_quota + data_service_memory_quota + index_service_memory_quota + \ + search_eventing_analytics_memory_quota_sum + + total_cpu_resources = data_cpu_limit + query_cpu_limit + index_cpu_limit + search_eventing_analytics_cpu_limit + + resources_info = dict(CN_COUCHBASE_DATA_NODES=int(number_of_data_nodes), + CN_COUCHBASE_QUERY_NODES=int(number_of_query_nodes), + CN_COUCHBASE_INDEX_NODES=int(number_of_index_nodes), + CN_COUCHBASE_SEARCH_EVENTING_ANALYTICS_NODES=int(number_of_eventing_service_memory_nodes), + COUCHBASE_DATA_MEM_QUOTA=round(data_service_memory_quota), + COUCHBASE_DATA_MEM_REQUEST=round(data_memory_request), + COUCHBASE_DATA_MEM_LIMIT=round(data_memory_limit), + COUCHBASE_DATA_CPU_REQUEST=round(data_cpu_request), + COUCHBASE_DATA_CPU_LIMIT=round(data_cpu_limit), + COUCHBASE_QUERY_MEM_QUOTA=round(data_service_memory_quota), + COUCHBASE_QUERY_MEM_REQUEST=round(query_memory_request), + COUCHBASE_QUERY_MEM_LIMIT=round(query_memory_limit), + COUCHBASE_QUERY_CPU_REQUEST=round(query_cpu_request), + COUCHBASE_QUERY_CPU_LIMIT=round(query_cpu_limit), + COUCHBASE_INDEX_MEM_QUOTA=round(index_service_memory_quota), + COUCHBASE_INDEX_MEM_REQUEST=round(index_memory_request), + COUCHBASE_INDEX_MEM_LIMIT=round(index_memory_limit), + COUCHBASE_INDEX_CPU_REQUEST=round(index_cpu_request), + COUCHBASE_INDEX_CPU_LIMIT=round(index_cpu_limit), + COUCHBASE_SEARCH_EVENTING_ANALYTICS_MEM_QUOTA=round(search_service_memory_quota), + COUCHBASE_SEARCH_EVENTING_ANALYTICS_MEM_REQUEST=round( + search_eventing_analytics_memory_request), + COUCHBASE_SEARCH_EVENTING_ANALYTICS_MEM_LIMIT=round( + search_eventing_analytics_memory_limit), + COUCHBASE_SEARCH_EVENTING_ANALYTICS_CPU_REQUEST=round( + search_eventing_analytics_cpu_request), + COUCHBASE_SEARCH_EVENTING_ANALYTICS_CPU_LIMIT=round(search_eventing_analytics_cpu_limit), + TOTAL_RAM_NEEDED=round(total_mem_resources), + TOTAL_CPU_NEEDED=round(total_cpu_resources) + ) + self.settings.set("CN_COUCHBASE_DATA_NODES", number_of_data_nodes) + self.settings.set("CN_COUCHBASE_QUERY_NODES", number_of_query_nodes) + self.settings.set("CN_COUCHBASE_INDEX_NODES", number_of_index_nodes) + self.settings.set("CN_COUCHBASE_SEARCH_EVENTING_ANALYTICS_NODES", number_of_eventing_service_memory_nodes) + return resources_info + + def analyze_couchbase_cluster_yaml(self): + """ + Dumps created calculated resources into couchbase.yaml file. ALso includes cloud zones. + """ + parser = Parser("./couchbase/couchbase-cluster.yaml", "CouchbaseCluster") + parser["metadata"]["name"] = self.settings.get("installer-settings.couchbase.clusterName") + number_of_buckets = 5 + if self.settings.get("global.storageClass.provisioner") in ("microk8s.io/hostpath", + "k8s.io/minikube-hostpath") or \ + self.settings.get("global.cloud.testEnviroment"): + resources_servers = [{"name": "allServices", "size": 1, + "services": ["data", "index", "query", "search", "eventing", "analytics"], + "volumeMounts": {"default": "pvc-general", + "data": "pvc-data", "index": "pvc-index", + "analytics": ["pvc-analytics"]}}] + data_service_memory_quota = 1024 + index_service_memory_quota = 512 + search_service_memory_quota = 512 + eventing_service_memory_quota = 512 + analytics_service_memory_quota = 1024 + memory_quota = 0 + else: + resources = self.calculate_couchbase_resources + data_service_memory_quota = resources["COUCHBASE_DATA_MEM_QUOTA"] + index_service_memory_quota = resources["COUCHBASE_INDEX_MEM_QUOTA"] + search_service_memory_quota = resources["COUCHBASE_SEARCH_EVENTING_ANALYTICS_MEM_QUOTA"] + eventing_service_memory_quota = resources["COUCHBASE_SEARCH_EVENTING_ANALYTICS_MEM_QUOTA"] + analytics_service_memory_quota = resources["COUCHBASE_SEARCH_EVENTING_ANALYTICS_MEM_QUOTA"] + 1024 + memory_quota = ((resources["COUCHBASE_DATA_MEM_QUOTA"] - 500) / number_of_buckets) + zones_list = self.settings.get("CN_NODES_ZONES") + data_server_spec = create_server_spec_per_cb_service(zones_list, int(resources["CN_COUCHBASE_DATA_NODES"]), + "data", + str(resources["COUCHBASE_DATA_MEM_REQUEST"]), + str(resources["COUCHBASE_DATA_MEM_LIMIT"]), + str(resources["COUCHBASE_DATA_CPU_REQUEST"]), + str(resources["COUCHBASE_DATA_CPU_LIMIT"])) + + query_server_spec = create_server_spec_per_cb_service(zones_list, + int(resources["CN_COUCHBASE_QUERY_NODES"]), + "query", + str(resources["COUCHBASE_QUERY_MEM_REQUEST"]), + str(resources["COUCHBASE_QUERY_MEM_LIMIT"]), + str(resources["COUCHBASE_QUERY_CPU_REQUEST"]), + str(resources["COUCHBASE_QUERY_CPU_LIMIT"])) + + index_server_spec = create_server_spec_per_cb_service(zones_list, + int(resources["CN_COUCHBASE_INDEX_NODES"]), "index", + str(resources["COUCHBASE_INDEX_MEM_REQUEST"]), + str(resources["COUCHBASE_INDEX_MEM_LIMIT"]), + str(resources["COUCHBASE_INDEX_CPU_REQUEST"]), + str(resources["COUCHBASE_INDEX_CPU_LIMIT"])) + + search_eventing_analytics_server_spec = create_server_spec_per_cb_service( + zones_list, + int(resources["CN_COUCHBASE_SEARCH_EVENTING_ANALYTICS_NODES"]), "analytics", + str(resources["COUCHBASE_SEARCH_EVENTING_ANALYTICS_MEM_REQUEST"]), + str(resources["COUCHBASE_SEARCH_EVENTING_ANALYTICS_MEM_LIMIT"]), + str(resources["COUCHBASE_SEARCH_EVENTING_ANALYTICS_CPU_REQUEST"]), + str(resources["COUCHBASE_SEARCH_EVENTING_ANALYTICS_CPU_LIMIT"])) + + resources_servers = \ + data_server_spec + query_server_spec + index_server_spec + \ + search_eventing_analytics_server_spec + + if self.settings.get("installer-settings.nodes.zones"): + unique_zones = list(dict.fromkeys(self.settings.get("installer-settings.nodes.zones"))) + parser["spec"]["serverGroups"] = unique_zones + parser["spec"]["cluster"]["dataServiceMemoryQuota"] = str(data_service_memory_quota) + "Mi" + parser["spec"]["cluster"]["indexServiceMemoryQuota"] = str(index_service_memory_quota) + "Mi" + parser["spec"]["cluster"]["searchServiceMemoryQuota"] = str(search_service_memory_quota) + "Mi" + parser["spec"]["cluster"]["eventingServiceMemoryQuota"] = str(eventing_service_memory_quota) + "Mi" + parser["spec"]["cluster"]["analyticsServiceMemoryQuota"] = str(analytics_service_memory_quota) + "Mi" + + set_memory_for_buckets(memory_quota, self.settings.get("config.configmap.cnCouchbaseBucketPrefix")) + parser["metadata"]["name"] = self.settings.get("installer-settings.couchbase.clusterName") + parser["spec"]["servers"] = resources_servers + + number_of_volume_claims = len(parser["spec"]["volumeClaimTemplates"]) + for i in range(number_of_volume_claims): + name = parser["spec"]["volumeClaimTemplates"][i]["metadata"]["name"] + if name == "pvc-general": + parser["spec"]["volumeClaimTemplates"][i]["spec"]["resources"]["requests"]["storage"] = "5Gi" + elif name == "pvc-data": + parser["spec"]["volumeClaimTemplates"][i]["spec"]["resources"]["requests"]["storage"] = "5Gi" + elif name == "pvc-index": + parser["spec"]["volumeClaimTemplates"][i]["spec"]["resources"]["requests"]["storage"] = "5Gi" + elif name == "pvc-query": + parser["spec"]["volumeClaimTemplates"][i]["spec"]["resources"]["requests"]["storage"] = "5Gi" + elif name == "pvc-analytics": + parser["spec"]["volumeClaimTemplates"][i]["spec"]["resources"]["requests"]["storage"] = "5Gi" + parser.dump_it() + + def install(self): + """ + Installs Couchbase + """ + self.kubernetes.create_namespace(name=self.settings.get("installer-settings.namespace")) + if not self.settings.get("installer-settings.couchbase.customFileOverride"): + try: + self.analyze_couchbase_cluster_yaml() + except Exception: + # TODO remove this exception + logger.error("Looks like some of the couchbase files were misconfigured. " + "If you wish to override the couchbase files please set " + " installer-settings.couchbase.customFileOverride to true`") + sys.exit() + cb_namespace = self.settings.get("installer-settings.couchbase.namespace") + storage_class_file_parser = Parser(self.storage_class_file, "StorageClass") + if self.settings.get('global.storageClass.provisioner') in ("kubernetes.io/gce-pd", + "dobs.csi.digitalocean.com", + "kubernetes.io/azure-disk"): + try: + del storage_class_file_parser["parameters"]["encrypted"] + except KeyError: + logger.info("Key not found") + storage_class_file_parser["parameters"]["type"] = \ + self.settings.get("installer-settings.couchbase.volumeType") + storage_class_file_parser["provisioner"] = self.settings.get('global.storageClass.provisioner') + if self.settings.get('global.storageClass.provisioner') == "microk8s.io/hostpath": + try: + del storage_class_file_parser["allowVolumeExpansion"] + del storage_class_file_parser["parameters"] + except KeyError: + logger.info("Key not found") + storage_class_file_parser.dump_it() + elif self.settings.get('global.storageClass.provisioner') == "k8s.io/minikube-hostpath": + try: + del storage_class_file_parser["allowVolumeExpansion"] + del storage_class_file_parser["parameters"] + except KeyError: + logger.info("Key not found") + storage_class_file_parser.dump_it() + else: + try: + storage_class_file_parser["parameters"]["type"] = \ + self.settings.get("installer-settings.couchbase.volumeType") + except KeyError: + logger.info("Key not found") + storage_class_file_parser.dump_it() + + logger.info("Installing Couchbase...") + couchbase_crts_keys = Path("couchbase_crts_keys") + if not couchbase_crts_keys.exists(): + os.mkdir(couchbase_crts_keys) + custom_cb_ca_crt = Path("./couchbase_crts_keys/ca.crt") + custom_cb_crt = Path("./couchbase_crts_keys/chain.pem") + custom_cb_key = Path("./couchbase_crts_keys/pkey.key") + if not custom_cb_ca_crt.exists() and not custom_cb_crt.exists() and not custom_cb_key.exists(): + setup_crts(ca_common_name=self.settings.get("installer-settings.couchbase.commonName"), + cert_common_name="couchbase-server", + san_list=self.settings.get("installer-settings.couchbase.subjectAlternativeName"), + ca_cert_file="./couchbase_crts_keys/ca.crt", + ca_key_file="./couchbase_crts_keys/ca.key", + cert_file="./couchbase_crts_keys/chain.pem", + key_file="./couchbase_crts_keys/pkey.key") + labels = {"app": "gluu-couchbase"} + if self.settings.get("global.istio.enabled"): + labels = {"app": "couchbase", "istio-injection": "enabled"} + self.kubernetes.create_namespace(name=cb_namespace, labels=labels) + chain_pem_filepath = Path("./couchbase_crts_keys/chain.pem") + pkey_filepath = Path("./couchbase_crts_keys/pkey.key") + tls_cert_filepath = Path("./couchbase_crts_keys/tls-cert-file") + tls_private_key_filepath = Path("./couchbase_crts_keys/tls-private-key-file") + ca_cert_filepath = Path("./couchbase_crts_keys/ca.crt") + shutil.copyfile(ca_cert_filepath, Path("./couchbase_crts_keys/couchbase.crt")) + shutil.copyfile(chain_pem_filepath, tls_cert_filepath) + shutil.copyfile(pkey_filepath, tls_private_key_filepath) + + encoded_ca_crt_string = self.settings.get("config.configmap.cnCouchbaseCrt") + if encoded_ca_crt_string in (None, ''): + with open(ca_cert_filepath) as content_file: + ca_crt_content = content_file.read() + encoded_ca_crt_bytes = base64.b64encode(ca_crt_content.encode("utf-8")) + encoded_ca_crt_string = str(encoded_ca_crt_bytes, "utf-8") + self.settings.set("config.configmap.cnCouchbaseCrt", encoded_ca_crt_string) + + with open(chain_pem_filepath) as content_file: + chain_pem_content = content_file.read() + encoded_chain_bytes = base64.b64encode(chain_pem_content.encode("utf-8")) + encoded_chain_string = str(encoded_chain_bytes, "utf-8") + + with open(pkey_filepath) as content_file: + pkey_content = content_file.read() + encoded_pkey_bytes = base64.b64encode(pkey_content.encode("utf-8")) + encoded_pkey_string = str(encoded_pkey_bytes, "utf-8") + + self.kubernetes.patch_or_create_namespaced_secret(name="couchbase-server-tls", + namespace=cb_namespace, + literal=chain_pem_filepath.name, + value_of_literal=encoded_chain_string, + second_literal=pkey_filepath.name, + value_of_second_literal=encoded_pkey_string) + self.kubernetes.patch_or_create_namespaced_secret(name="couchbase-operator-tls", + namespace=cb_namespace, + literal=ca_cert_filepath.name, + value_of_literal=encoded_ca_crt_string) + + encoded_cb_super_user_bytes = base64.b64encode( + self.settings.get("config.configmap.cnCouchbaseSuperUser").encode("utf-8")) + encoded_cb_super_user_string = str(encoded_cb_super_user_bytes, "utf-8") + encoded_cb_pass_bytes = base64.b64encode( + self.settings.get("config.configmap.cnCouchbasePassword").encode("utf-8")) + encoded_cb_pass_string = str(encoded_cb_pass_bytes, "utf-8") + encoded_cb_super_pass_bytes = base64.b64encode( + self.settings.get("config.configmap.cnCouchbaseSuperUserPassword").encode("utf-8")) + encoded_cb_super_pass_string = str(encoded_cb_super_pass_bytes, "utf-8") + + self.create_couchbase_gluu_cert_pass_secrets(encoded_ca_crt_string, encoded_cb_pass_string, + encoded_cb_super_pass_string) + self.kubernetes.patch_or_create_namespaced_secret(name="gluu-couchbase-user-password", + namespace=self.settings.get( + "installer-settings.couchbase.namespace"), + literal="password", + value_of_literal=encoded_cb_pass_string) + + admission_command = "./{}/bin/cbopcfg generate admission --namespace {}".format(self.couchbase_source_file, + self.settings.get( + "installer-settings.couchbase.namespace")) + operator_command = "./{}/bin/cbopcfg generate operator --namespace {}".format(self.couchbase_source_file, + self.settings.get( + "installer-settings.couchbase.namespace")) + backup_command = "./{}/bin/cbopcfg generate backup --namespace {}".format(self.couchbase_source_file, + self.settings.get( + "installer-settings.couchbase.namespace")) + # @TODO: Remove condition and operator_command override after depreciation of couchbase operator 2.0 + if self.old_couchbase: + operator_command = "./{}/bin/cbopcfg -backup=true -namespace={}".format(self.couchbase_source_file, + self.settings.get( + "installer-settings.couchbase.namespace")) + exec_cmd(operator_command, output_file=self.couchbase_operator_dac_file) + # @TODO: Remove only the condition after depreciation of couchbase operator 2.0 + if not self.old_couchbase: + exec_cmd(backup_command, output_file=self.couchbase_operator_backup_file) + exec_cmd(admission_command, output_file=self.couchbase_admission_file) + + couchbase_cluster_parser = Parser(self.couchbase_cluster_file, "CouchbaseCluster") + couchbase_cluster_parser["spec"]["networking"]["tls"]["static"]["serverSecret"] = "couchbase-server-tls" + couchbase_cluster_parser["spec"]["networking"]["tls"]["static"]["operatorSecret"] = "couchbase-operator-tls" + if self.settings.get("global.istio.enabled"): + couchbase_cluster_parser["spec"]["networking"]["networkPlatform"] = "Istio" + try: + couchbase_cluster_parser["spec"]["security"]["rbac"]["selector"]["matchLabels"]["cluster"] = \ + self.settings.get("installer-settings.couchbase.clusterName") + couchbase_cluster_parser["spec"]["security"]["rbac"]["managed"] = True + except KeyError: + logger.error("rbac section is missing or incorrect in couchbase-cluster.yaml." + " Please set spec --> security --> rbac --> managed : true" + " and set spec --> security --> rbac --> selector --> matchLabels --> " + "cluster --> to your cluster name") + logger.info("As a result of the above the installation will exit " + "as the gluu user will not be created causing the communication between " + "Gluu server and Couchbase to fail.") + sys.exit() + if "localOpenEbsHostPathDynamic" in self.settings.get("installer-settings.volumeProvisionStrategy"): + volume_claims = couchbase_cluster_parser["spec"]["volumeClaimTemplates"] + for i, volume_claim in enumerate(volume_claims): + couchbase_cluster_parser["spec"]["volumeClaimTemplates"][i]["spec"]["storageClassName"] = \ + "openebs-hostpath" + couchbase_cluster_parser.dump_it() + + self.kubernetes.create_objects_from_dict(self.couchbase_custom_resource_definition_file, + namespace=cb_namespace) + + self.kubernetes.create_objects_from_dict(self.couchbase_operator_dac_file, + namespace=cb_namespace) + # @TODO: Remove only the condition after depreciation of couchbase operator 2.0 + if not self.old_couchbase: + self.kubernetes.create_objects_from_dict(self.couchbase_admission_file, + namespace=cb_namespace) + + self.kubernetes.create_objects_from_dict(self.couchbase_operator_backup_file, + namespace=cb_namespace) + + self.kubernetes.check_pods_statuses(cb_namespace, "app=couchbase-operator", 700) + + self.kubernetes.patch_or_create_namespaced_secret(name="cb-auth", + namespace=cb_namespace, + literal="username", + value_of_literal=encoded_cb_super_user_string, + second_literal="password", + value_of_second_literal=encoded_cb_super_pass_string) + + self.kubernetes.create_objects_from_dict(self.storage_class_file, namespace=cb_namespace) + self.kubernetes.create_namespaced_custom_object(filepath=self.couchbase_cluster_file, + group="couchbase.com", + version="v2", + plural="couchbaseclusters", + namespace=cb_namespace) + self.kubernetes.create_namespaced_custom_object(filepath=self.couchbase_buckets_file, + group="couchbase.com", + version="v2", + plural="couchbasebuckets", + namespace=cb_namespace) + self.kubernetes.create_namespaced_custom_object(filepath=self.couchbase_ephemeral_buckets_file, + group="couchbase.com", + version="v2", + plural="couchbaseephemeralbuckets", + namespace=cb_namespace) + coucbase_group_parser = Parser(self.couchbase_group_file, "CouchbaseGroup") + coucbase_group_parser["metadata"]["labels"]["cluster"] = \ + self.settings.get("installer-settings.couchbase.clusterName") + permissions = ["query_select", "query_update", "query_insert", "query_delete"] + allbuckets = ["", "site", "user", "cache", "token", "session"] + roles = [] + for permission in permissions: + for bucket in allbuckets: + bucket_name = self.settings.get("config.configmap.cnCouchbaseBucketPrefix") + if bucket: + bucket_name = bucket_name + "_" + bucket + roles.append({"name": permission, "bucket": bucket_name}) + coucbase_group_parser["spec"]["roles"] = roles + coucbase_group_parser.dump_it() + coucbase_user_parser = Parser(self.couchbase_user_file, "CouchbaseUser") + coucbase_user_parser["metadata"]["labels"]["cluster"] = \ + self.settings.get("installer-settings.couchbase.clusterName") + coucbase_user_parser.dump_it() + self.kubernetes.create_namespaced_custom_object(filepath=self.couchbase_group_file, + group="couchbase.com", + version="v2", + plural="couchbasegroups", + namespace=cb_namespace) + self.kubernetes.create_namespaced_custom_object(filepath=self.couchbase_user_file, + group="couchbase.com", + version="v2", + plural="couchbaseusers", + namespace=cb_namespace) + self.kubernetes.create_namespaced_custom_object(filepath=self.couchbase_rolebinding_file, + group="couchbase.com", + version="v2", + plural="couchbaserolebindings", + namespace=cb_namespace) + self.kubernetes.check_pods_statuses(cb_namespace, "couchbase_service_analytics=enabled", 700) + self.kubernetes.check_pods_statuses(cb_namespace, "couchbase_service_data=enabled", 700) + self.kubernetes.check_pods_statuses(cb_namespace, "couchbase_service_eventing=enabled", 700) + self.kubernetes.check_pods_statuses(cb_namespace, "couchbase_service_index=enabled", 700) + self.kubernetes.check_pods_statuses(cb_namespace, "couchbase_service_query=enabled", 700) + self.kubernetes.check_pods_statuses(cb_namespace, "couchbase_service_search=enabled", 700) + # Setup couchbase backups + if self.settings.get("global.storageClass.provisioner") not in ("microk8s.io/hostpath", + "k8s.io/minikube-hostpath"): + self.setup_backup_couchbase() + shutil.rmtree(self.couchbase_source_folder_pattern, ignore_errors=True) + + def uninstall(self): + """ + Uninstalls couchbase + """ + logger.info("Deleting Couchbase...") + self.kubernetes.delete_storage_class("couchbase-sc") + self.kubernetes.delete_custom_resource("couchbaseclusters.couchbase.com") + self.kubernetes.delete_validating_webhook_configuration("couchbase-operator-admission") + self.kubernetes.delete_mutating_webhook_configuration("couchbase-operator-admission") + self.kubernetes.delete_cluster_role_binding("couchbase-operator-admission") + self.kubernetes.delete_cluster_role("couchbase-operator-admission") + self.kubernetes.delete_role("couchbase-operator", self.settings.get("installer-settings.couchbase.namespace")) + self.kubernetes.delete_secret("cb-auth", self.settings.get("installer-settings.couchbase.namespace")) + self.kubernetes.delete_secret("gluu-couchbase-user-password", self.settings.get("installer-settings.couchbase.namespace")) + self.kubernetes.delete_deployment_using_name("couchbase-operator", self.settings.get("installer-settings.couchbase.namespace")) + self.kubernetes.delete_role_binding("couchbase-operator", self.settings.get("installer-settings.couchbase.namespace")) + self.kubernetes.delete_service_account("couchbase-operator", self.settings.get("installer-settings.couchbase.namespace")) + self.kubernetes.delete_service("couchbase-operator-admission", self.settings.get("installer-settings.couchbase.namespace")) + self.kubernetes.delete_deployment_using_name("couchbase-operator-admission", + self.settings.get("installer-settings.couchbase.namespace")) + self.kubernetes.delete_service("couchbase-operator", self.settings.get("installer-settings.couchbase.namespace")) + self.kubernetes.delete_custom_resource("couchbasebackuprestores.couchbase.com") + self.kubernetes.delete_custom_resource("couchbasebackups.couchbase.com") + self.kubernetes.delete_custom_resource("couchbasebuckets.couchbase.com") + self.kubernetes.delete_custom_resource("couchbaseephemeralbuckets.couchbase.com") + self.kubernetes.delete_custom_resource("couchbasereplications.couchbase.com") + self.kubernetes.delete_custom_resource("couchbaserolebindings.couchbase.com") + self.kubernetes.delete_custom_resource("couchbasegroups.couchbase.com") + self.kubernetes.delete_custom_resource("couchbasememcachedbuckets.couchbase.com") + self.kubernetes.delete_custom_resource("couchbaseusers.couchbase.com") + self.kubernetes.delete_custom_resource("couchbaseautoscalers.couchbase.com") + + self.kubernetes.delete_service_account("couchbase-operator-admission", + self.settings.get("installer-settings.couchbase.namespace")) + self.kubernetes.delete_secret("couchbase-operator-admission", self.settings.get("installer-settings.couchbase.namespace")) + self.kubernetes.delete_secret("couchbase-operator-tls", self.settings.get("installer-settings.couchbase.namespace")) + shutil.rmtree(Path("./couchbase-source-folder"), ignore_errors=True) diff --git a/helm/pygluu/kubernetes/create.py b/helm/pygluu/kubernetes/create.py new file mode 100644 index 00000000000..9036889bf2f --- /dev/null +++ b/helm/pygluu/kubernetes/create.py @@ -0,0 +1,148 @@ +""" + License terms and conditions for Gluu Cloud Native Edition: + https://www.apache.org/licenses/LICENSE-2.0 + Installs Gluu +""" +import argparse +import sys, shutil +from pathlib import Path +from pygluu.kubernetes.couchbase import Couchbase +from pygluu.kubernetes.terminal.prompt import Prompt +from pygluu.kubernetes.helpers import get_logger, copy_templates +from pygluu.kubernetes.gluu import Gluu +from pygluu.kubernetes.settings import ValuesHandler + +logger = get_logger("gluu-create ") + + +def create_parser(): + """Create parser to handle arguments from CLI. + :return: + """ + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(title="Commands", dest="subparser_name") + subparsers.add_parser("generate-settings", help="Generate settings.json to install " + "Gluu Cloud Native Edition non-interactively") + subparsers.add_parser("install-ldap-backup", help="Install ldap backup cronjob only.") + subparsers.add_parser("restore", help="Install Gluu Cloud Native Edition with a " + "running database and previous configuration") + subparsers.add_parser("upgrade", help="Upgrade Gluu Cloud Native Edition") + subparsers.add_parser("upgrade-values-yaml", help="Upgrade Gluu Cloud Native Edition") + subparsers.add_parser("install-couchbase", help="Install Couchbase only. Used with installation of Gluu with Helm") + subparsers.add_parser("install-couchbase-backup", help="Install Couchbase backup only.") + subparsers.add_parser("uninstall-couchbase", help="Uninstall Couchbase only.") + subparsers.add_parser("install", help="Install Gluu Cloud Native Edition using helm. " + "This also installs the nginx-ingress chart") + subparsers.add_parser("uninstall", help="Uninstall Gluu Cloud Native Edition using helm. " + "This also uninstalls the nginx-ingress chart") + subparsers.add_parser("install-gluu", help="Install Gluu Cloud Native Edition using helm. " + "This assumes nginx-ingress is installed") + subparsers.add_parser("uninstall-gluu", help="Uninstall Gluu Cloud Native Edition using helm." + "This only uninstalls Gluu") + subparsers.add_parser("version", help="Outputs version of pygluu installer.") + return parser + + +def main(): + parser = create_parser() + args = parser.parse_args(sys.argv[1:]) + + if not args.subparser_name: + parser.print_help() + return + + if args.subparser_name == "version": + from pygluu.kubernetes import __version__ + logger.info(f"pygluu installer version is : {__version__}") + return + + copy_templates() + # Prepare override-values.yaml for parsing + shutil.copy(Path("./helm/gluu/values.yaml"), Path("./helm/gluu/override-values.yaml")) + settings = ValuesHandler() + settings.load() + prompts = Prompt() + prompts.prompt() + settings = ValuesHandler() + + try: + + if args.subparser_name == "uninstall-gluu": + gluu = Gluu() + gluu.uninstall_gluu() + if settings.get("installer-settings.redis.install"): + # TODO: Make sure remove redis or postgres if installled by Gluu + logger.info("remove me after implementing TODO") + elif args.subparser_name == "upgrade-values-yaml": + from pygluu.kubernetes.terminal.upgrade import PromptUpgrade + # New feature in 4.2 compared to 4.1 and hence if enabled should make sure postgres is installed. + gluu = Gluu() + if settings.get("installer-settings.jackrabbit.clusterMode") and \ + settings.get("installer-settings.postgres.install"): + # TODO: Make sure postgres is installed + logger.info("remove me after implementing TODO") + prompt_upgrade = PromptUpgrade(settings) + prompt_upgrade.prompt_upgrade() + logger.info("Patching values.yaml for helm upgrade...") + logger.info("Please find your patched values.yaml at the location ./helm/gluu/values.yaml." + "Continue with the steps found at https://gluu.org/docs/gluu-server/latest/upgrade/#helm") + + elif args.subparser_name == "install-couchbase": + from pygluu.kubernetes.terminal.couchbase import PromptCouchbase + prompt_couchbase = PromptCouchbase(settings) + prompt_couchbase.prompt_couchbase() + couchbase = Couchbase() + couchbase.install() + + elif args.subparser_name == "install-couchbase-backup": + from pygluu.kubernetes.terminal.couchbase import PromptCouchbase + prompt_couchbase = PromptCouchbase(settings) + prompt_couchbase.prompt_couchbase() + couchbase = Couchbase() + couchbase.setup_backup_couchbase() + + elif args.subparser_name == "uninstall-couchbase": + from pygluu.kubernetes.terminal.couchbase import PromptCouchbase + prompt_couchbase = PromptCouchbase(settings) + prompt_couchbase.prompt_couchbase() + couchbase = Couchbase() + couchbase.uninstall() + + elif args.subparser_name == "generate-settings": + logger.info("settings.json has been generated") + + elif args.subparser_name == "install": + gluu = Gluu() + if settings.get("installer-settings.postgres.install"): + from pygluu.kubernetes.postgres import Postgres + postgres = Postgres() + postgres.install_postgres() + if settings.get("installer-settings.redis.install"): + from pygluu.kubernetes.redis import Redis + redis = Redis() + redis.uninstall_redis() + redis.install_redis() + if settings.get("installer-settings.sql.install") and \ + settings.get("config.configmap.cnSqlDbDialect") == "mysql": + from pygluu.kubernetes.mysql import MySQL + sql = MySQL() + sql.install_mysql() + gluu.install_gluu() + + elif args.subparser_name == "uninstall": + gluu = Gluu() + gluu.uninstall_gluu() + gluu.uninstall_nginx_ingress() + logger.info("Please wait...") + + elif args.subparser_name == "install-gluu": + gluu = Gluu() + gluu.uninstall_gluu() + gluu.install_gluu(install_ingress=False) + + except KeyboardInterrupt: + print("\n[I] Canceled by user; exiting ...") + + +if __name__ == "__main__": + main() diff --git a/helm/pygluu/kubernetes/gluu.py b/helm/pygluu/kubernetes/gluu.py new file mode 100644 index 00000000000..0051b711e77 --- /dev/null +++ b/helm/pygluu/kubernetes/gluu.py @@ -0,0 +1,275 @@ +""" +pygluu.kubernetes.helm +~~~~~~~~~~~~~~~~~~~~~~ + + License terms and conditions for Gluu Cloud Native Edition: + https://www.apache.org/licenses/LICENSE-2.0 + Handles Helm Gluu Chart +""" + +from pathlib import Path +from pygluu.kubernetes.yamlparser import Parser +from pygluu.kubernetes.helpers import get_logger, exec_cmd +from pygluu.kubernetes.kubeapi import Kubernetes +from pygluu.kubernetes.couchbase import Couchbase +from pygluu.kubernetes.settings import ValuesHandler +import time +import socket + +logger = get_logger("gluu-helm ") + + +class Gluu(object): + def __init__(self): + self.values_file = Path("./helm/gluu/override-values.yaml").resolve() + self.upgrade_values_file = Path("./helm/gluu-upgrade/values.yaml").resolve() + self.settings = ValuesHandler() + self.kubernetes = Kubernetes() + self.ldap_backup_release_name = self.settings.get("installer-settings.releaseName") + "-ldap-backup" + if "gke" in self.settings.get("installer-settings.volumeProvisionStrategy"): + # Clusterrolebinding needs to be created for gke with CB installed + if self.settings.get("config.configmap.cnCacheType") == "REDIS" or \ + self.settings.get("installer-settings.couchbase.install"): + user_account, stderr, retcode = exec_cmd("gcloud config get-value core/account") + user_account = str(user_account, "utf-8").strip() + + user, stderr, retcode = exec_cmd("whoami") + user = str(user, "utf-8").strip() + cluster_role_binding_name = "cluster-admin-{}".format(user) + self.kubernetes.create_cluster_role_binding(cluster_role_binding_name=cluster_role_binding_name, + user_name=user_account, + cluster_role_name="cluster-admin") + + def prepare_alb(self): + ingress_parser = Parser("./alb/ingress.yaml", "Ingress") + ingress_parser["spec"]["rules"][0]["host"] = self.settings.get("global.fqdn") + ingress_parser["metadata"]["annotations"]["alb.ingress.kubernetes.io/certificate-arn"] = \ + self.settings.get("installer-settings.aws.arn.arnAcmCert") + if not self.settings.get("installer-settings.aws.arn.enabled"): + del ingress_parser["metadata"]["annotations"]["alb.ingress.kubernetes.io/certificate-arn"] + + for path in ingress_parser["spec"]["rules"][0]["http"]["paths"]: + service_name = path["backend"]["serviceName"] + if self.settings.get("config.configmap.cnCasaEnabled") and service_name == "casa": + path_index = ingress_parser["spec"]["rules"][0]["http"]["paths"].index(path) + del ingress_parser["spec"]["rules"][0]["http"]["paths"][path_index] + + if self.settings.get("global.oxshibboleth.enabled") and service_name == "oxshibboleth": + path_index = ingress_parser["spec"]["rules"][0]["http"]["paths"].index(path) + del ingress_parser["spec"]["rules"][0]["http"]["paths"][path_index] + + if self.settings.get("config.configmap.cnPassportEnabled") and service_name == "oxpassport": + path_index = ingress_parser["spec"]["rules"][0]["http"]["paths"].index(path) + del ingress_parser["spec"]["rules"][0]["http"]["paths"][path_index] + + if self.settings.get("installer-settings.global.scim.enabled") and service_name == "jans-scim": + path_index = ingress_parser["spec"]["rules"][0]["http"]["paths"].index(path) + del ingress_parser["spec"]["rules"][0]["http"]["paths"][path_index] + + if self.settings.get("installer-settings.config-api.enabled") and service_name == "config-api": + path_index = ingress_parser["spec"]["rules"][0]["http"]["paths"].index(path) + del ingress_parser["spec"]["rules"][0]["http"]["paths"][path_index] + + ingress_parser.dump_it() + + def deploy_alb(self): + alb_ingress = Path("./alb/ingress.yaml") + self.kubernetes.create_objects_from_dict(alb_ingress, self.settings.get("installer-settings.namespace")) + if self.settings.get("global.fqdn"): + prompt = input("Please input the DNS of the Application load balancer found on AWS UI: ") + lb_hostname = prompt + while True: + try: + if lb_hostname: + break + lb_hostname = self.kubernetes.read_namespaced_ingress( + name="gluu", namespace="gluu").status.load_balancer.ingress[0].hostname + except TypeError: + logger.info("Waiting for loadbalancer address..") + time.sleep(10) + self.settings.set("config.configmap.lbAddr", lb_hostname) + + def wait_for_nginx_add(self): + hostname_ip = None + while True: + try: + if hostname_ip: + break + if "aws" in self.settings.get("installer-settings.volumeProvisionStrategy"): + hostname_ip = self.kubernetes.read_namespaced_service( + name=self.settings.get( + 'installer-settings.nginxIngress.releaseName') + "-ingress-nginx-controller", + namespace=self.settings.get( + "installer-settings.nginxIngress.releaseName")).status.load_balancer.ingress[ + 0].hostname + self.settings.set("config.configmap.lbAddr", hostname_ip) + if self.settings.get("installer-settings.aws.lbType") == "nlb": + try: + ip_static = socket.gethostbyname(str(hostname_ip)) + if ip_static: + break + except socket.gaierror: + logger.info("Address has not received an ip yet.") + elif "local" in self.settings.get("installer-settings.volumeProvisionStrategy"): + self.settings.set("config.configmap.lbAddr", + self.settings.get('installer-settings.nginxIngress.releaseName') + + "-nginx-ingress-controller." + + self.settings.get("installer-settings.nginxIngress.releaseName") + + ".svc.cluster.local") + break + else: + hostname_ip = self.kubernetes.read_namespaced_service( + name=self.settings.get('installer-settings.nginxIngress.releaseName') + "-ingress-nginx-controller", + namespace=self.settings.get("installer-settings.nginxIngress.releaseName")).status.load_balancer.ingress[0].ip + self.settings.set("global.lbIp", hostname_ip) + except (TypeError, AttributeError): + logger.info("Waiting for address..") + time.sleep(10) + + def check_install_nginx_ingress(self, install_ingress=True): + """ + Helm installs nginx ingress or checks to recieve and ip or address + :param install_ingress: + """ + if install_ingress: + self.kubernetes.delete_custom_resource("virtualservers.k8s.nginx.org") + self.kubernetes.delete_custom_resource("virtualserverroutes.k8s.nginx.org") + self.kubernetes.delete_cluster_role("ingress-nginx-nginx-ingress") + self.kubernetes.delete_cluster_role_binding("ingress-nginx-nginx-ingress") + self.kubernetes.create_namespace(name=self.settings.get("installer-settings.nginxIngress.releaseName"), + labels={"app": "ingress-nginx"}) + self.kubernetes.delete_cluster_role( + self.settings.get('installer-settings.nginxIngress.releaseName') + "-nginx-ingress-controller") + self.kubernetes.delete_cluster_role_binding( + self.settings.get('installer-settings.nginxIngress.releaseName') + "-nginx-ingress-controller") + try: + exec_cmd("helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx") + exec_cmd("helm repo add stable https://charts.helm.sh/stable") + exec_cmd("helm repo update") + except FileNotFoundError: + logger.error("Helm v3 is not installed. Please install it to continue " + "https://helm.sh/docs/intro/install/") + raise SystemExit(1) + command = "helm install {} ingress-nginx/ingress-nginx --namespace={} ".format( + self.settings.get('installer-settings.nginxIngress.releaseName'), + self.settings.get("installer-settings.nginxIngress.namespace")) + if self.settings.get("installer-settings.volumeProvisionStrategy") == "minikubeDynamic": + exec_cmd("minikube addons enable ingress") + if "aws" in self.settings.get("installer-settings.volumeProvisionStrategy"): + if self.settings.get("installer-settings.aws.lbType") == "nlb": + if install_ingress: + nlb_override_values_file = Path("./nginx/aws/aws-nlb-override-values.yaml").resolve() + nlb_values = " --values {}".format(nlb_override_values_file) + exec_cmd(command + nlb_values) + else: + if self.settings.get("installer-settings.aws.arn.enabled"): + if install_ingress: + elb_override_values_file = Path("./nginx/aws/aws-elb-override-values.yaml").resolve() + elb_file_parser = Parser(elb_override_values_file, True) + elb_file_parser["controller"]["service"]["annotations"].update( + {"service.beta.kubernetes.io/aws-load-balancer-ssl-cert": self.settings.get( + "installer-settings.aws.arn.arnAcmCert")}) + elb_file_parser["controller"]["config"]["proxy-real-ip-cidr"] = \ + self.settings.get("installer-settings.aws.vpcCidr") + elb_file_parser.dump_it() + elb_values = " --values {}".format(elb_override_values_file) + exec_cmd(command + elb_values) + else: + if install_ingress: + exec_cmd(command) + volume_provision_strategy = self.settings.get("installer-settings.volumeProvisionStrategy") + if "gke" in volume_provision_strategy or \ + "aks" in volume_provision_strategy or \ + "doks" in volume_provision_strategy: + if install_ingress: + cloud_override_values_file = Path("./nginx/cloud/cloud-override-values.yaml").resolve() + cloud_values = " --values {}".format(cloud_override_values_file) + exec_cmd(command + cloud_values) + elif "local" in volume_provision_strategy: + if install_ingress: + baremetal_override_values_file = Path("./nginx/baremetal/baremetal-override-values.yaml").resolve() + baremetal_values = " --values {}".format(baremetal_override_values_file) + exec_cmd(command + baremetal_values) + if self.settings.get("global.storageClass.provisioner") not in \ + ("microk8s.io/hostpath", "k8s.io/minikube-hostpath"): + logger.info("Waiting for nginx to be prepared...") + time.sleep(60) + self.wait_for_nginx_add() + + def install_gluu(self, install_ingress=True): + """ + Helm install Gluu + :param install_ingress: + """ + labels = {"app": "gluu"} + if self.settings.get("global.istio.enabled"): + labels = {"app": "gluu", "istio-injection": "enabled"} + self.kubernetes.create_namespace(name=self.settings.get("installer-settings.namespace"), labels=labels) + if self.settings.get("global.cnPersistenceType") != "ldap" and \ + self.settings.get("installer-settings.couchbase.install"): + couchbase_app = Couchbase() + couchbase_app.uninstall() + couchbase_app = Couchbase() + couchbase_app.install() + self.settings = ValuesHandler() + if self.settings.get("installer-settings.aws.lbType") == "alb": + self.prepare_alb() + self.deploy_alb() + if self.settings.get("installer-settings.aws.lbType") != "alb" and \ + self.settings.get("global.istio.ingress"): + self.check_install_nginx_ingress(install_ingress) + try: + exec_cmd("helm install {} -f {} ./helm/gluu --namespace={}".format( + self.settings.get('installer-settings.releaseName'), + self.values_file, self.settings.get("installer-settings.namespace"))) + + if self.settings.get("global.cnPersistenceType") in ("hybrid", "ldap"): + self.install_ldap_backup() + + except FileNotFoundError: + logger.error("Helm v3 is not installed. Please install it to continue " + "https://helm.sh/docs/intro/install/") + raise SystemExit(1) + + def install_ldap_backup(self): + values_file = Path("./helm/ldap-backup/values.yaml").resolve() + values_file_parser = Parser(values_file, True) + values_file_parser["ldapPass"] = self.settings.get("config.ldapPassword") + if self.settings.get("global.storageClass.provisioner") not in \ + ("microk8s.io/hostpath", "k8s.io/minikube-hostpath"): + values_file_parser["gluuLdapSchedule"] = self.settings.get("installer-settings.ldap.backup.fullSchedule") + if self.settings.get("opendj.multiCluster.enabled"): + values_file_parser["multiCluster"]["enabled"] = True + values_file_parser["multiCluster"]["ldapAdvertiseAdminPort"] = \ + self.settings.get("opendj.ports.tcp-admin.nodePort") + values_file_parser["multiCluster"]["serfAdvertiseAddrSuffix"] = \ + self.settings.get("opendj.multiCluster.serfAdvertiseAddrSuffix")[:-6] + values_file_parser.dump_it() + exec_cmd("helm install {} -f ./helm/ldap-backup/values.yaml ./helm/ldap-backup --namespace={}".format( + self.ldap_backup_release_name, self.settings.get("installer-settings.namespace"))) + + def upgrade_gluu(self): + values_file_parser = Parser(self.upgrade_values_file, True) + values_file_parser["domain"] = self.settings.get("global.fqdn") + values_file_parser["cnCacheType"] = self.settings.get("config.configmap.cnCacheType") + values_file_parser["cnCouchbaseUrl"] = self.settings.get("config.configmap.cnCouchbaseUrl") + values_file_parser["cnCouchbaseUser"] = self.settings.get("config.configmap.cnCouchbaseUser") + values_file_parser["cnCouchbaseSuperUser"] = self.settings.get("config.configmap.cnCouchbaseSuperUser") + values_file_parser["cnPersistenceLdapMapping"] = self.settings.get("global.cnPersistenceType") + values_file_parser["cnPersistenceType"] = self.settings.get("config.configmap.cnPersistenceLdapMapping") + values_file_parser["source"] = self.settings.get("installer-settings.currentVersion") + values_file_parser["target"] = self.settings.get("installer-settings.upgrade.targetVersion") + values_file_parser.dump_it() + exec_cmd("helm install {} -f {} ./helm/gluu-upgrade --namespace={}".format( + self.settings.get('installer-settings.releaseName'), self.values_file, + self.settings.get("installer-settings.namespace"))) + + def uninstall_gluu(self): + exec_cmd("helm delete {} --namespace={}".format(self.settings.get('installer-settings.releaseName'), + self.settings.get("installer-settings.namespace"))) + exec_cmd("helm delete {} --namespace={}".format(self.ldap_backup_release_name, + self.settings.get("installer-settings.namespace"))) + + def uninstall_nginx_ingress(self): + exec_cmd("helm delete {} --namespace={}".format(self.settings.get('installer-settings.nginxIngress.releaseName'), + self.settings.get("installer-settings.nginxIngress.namespace"))) diff --git a/helm/pygluu/kubernetes/helpers.py b/helm/pygluu/kubernetes/helpers.py new file mode 100644 index 00000000000..567bcab14b5 --- /dev/null +++ b/helm/pygluu/kubernetes/helpers.py @@ -0,0 +1,208 @@ +""" +pygluu.kubernetes.common +~~~~~~~~~~~~~~~~~~~~~~~~ + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import errno +import subprocess +import shlex +import logging +import json +import shutil +import os +import string +import random +import re +from getpass import getpass +from pathlib import Path + + +def update_settings_json_file(settings): + """Write settings out to a json file + + :param settings: + """ + with open(Path('./settings.json'), 'w+') as file: + json.dump(settings, file, indent=2) + + +def exec_cmd(cmd, output_file=None, silent=False): + """Execute command cmd + + :param cmd: + :param output_file: + :param silent: + :return: + """ + args = shlex.split(cmd) + popen = subprocess.Popen(args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = popen.communicate() + retcode = popen.returncode + if stdout and output_file: + with open(output_file, "w+") as file: + file.write(str(stdout, "utf-8")) + else: + logger.info(str(stdout, "utf-8")) + if retcode != 0 and not silent: + logger.error(str(stderr, "utf-8")) + return stdout, stderr, retcode + + +def get_logger(name): + """Set logger configs with name. + + :param name: + :return: + """ + log_format = '%(asctime)s - %(name)8s - %(levelname)5s - %(message)s' + logging.basicConfig(level=logging.INFO, + format=log_format, + filename='setup.log', + filemode='w') + console = logging.StreamHandler() + console.setLevel(logging.INFO) + console.setFormatter(logging.Formatter(log_format)) + logging.getLogger(name).addHandler(console) + return logging.getLogger(name) + + +def copy_templates(): + """Copy templates folder. /pygluu/kubernetes/templates to working dir. + """ + entries = Path( + os.path.join(os.path.dirname(__file__), "templates") + ) + curdir = os.getcwd() + for entry in entries.iterdir(): + dst = os.path.join(curdir, entry.name) + if os.path.exists(dst): + continue + copy(entry, dst) + + +def check_microk8s_kube_config_file(): + """Copy microk8s kuber config to ~/.kube/config + """ + kube_config_file_location = Path(os.path.expanduser("~/.kube/config")) + + if not kube_config_file_location.exists(): + kube_dir = os.path.dirname(kube_config_file_location) + + if not os.path.exists(kube_dir): + os.makedirs(kube_dir) + + try: + shutil.copy(Path("/var/snap/microk8s/current/credentials/client.config"), kube_config_file_location) + except FileNotFoundError: + logger.error("No Kubernetes config file found at ~/.kube/config") + + +def get_supported_versions(): + """Get Gluu versions from gluu_versions.json + + return: + """ + versions = {} + version_number = 0 + dev_version = "" + + filename = Path("./gluu_versions.json") + try: + with open(filename) as f: + versions = json.load(f) + logger.info("Currently supported versions are : ") + for k, v in versions.items(): + if "_dev" in k: + logger.info("Development version : {}".format(k)) + dev_version = k + else: + logger.info("Stable version : {}".format(k)) + if float(k) > version_number: + version_number = float(k) + except FileNotFoundError: + pass + finally: + if not version_number: + # No stable version exists + version_number = dev_version + + version_number = str(version_number) + return versions, version_number + + +def generate_password(length=6): + """Returns randomly generated password + + :param length: Length of password + :return: + """ + chars = string.ascii_letters + string.digits + string.punctuation + string.punctuation + chars = chars.replace('"', '') + chars = chars.replace("'", "") + chars = chars.replace("$", "") + chars = chars.replace("/", "") + chars = chars.replace("\\", "") + chars = chars.replace("!", "") + + while True: + password = ''.join(random.choice(chars) for _ in range(length)) + regex_bool = re.match('^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*\W)[a-zA-Z0-9\S]{6,}$', password) # noqa: W605 + if regex_bool: + break + + return password + + +def prompt_password(password, length=6): + """Prompt password and password confirmation + + :param password: string for the prompt name + :param length: Length of password + :return: + """ + while True: + random_password = "" if password == "Redis" else generate_password(length) + string_random_password = '' if not random_password else random_password[:1] + "***" + random_password[4:] + pw_prompt = getpass(prompt='{} password [{}]: '.format(password, string_random_password), stream=None) + regex_bool = True + if not pw_prompt: + pw_prompt = random_password + confirm_pw_prompt = random_password + else: + confirm_pw_prompt = getpass(prompt='Confirm password: ', stream=None) + if password != "Redis": + regex_bool = re.match('^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*\\W)[a-zA-Z0-9\\S]{6,}$', + pw_prompt) # noqa: W605 + + if confirm_pw_prompt != pw_prompt: + logger.error("Passwords do not match") + elif not regex_bool: + logger.error("Password does not meet requirements. The password must contain one digit, one uppercase" + " letter, one lower case letter and one symbol") + else: + logger.info("Success! {} password was set.".format(password)) + return pw_prompt + + +def copy(src, dest): + """Copy from source to destination + + :param src: + :param dest: + """ + try: + shutil.copytree(src, dest) + except OSError as e: + # If the error was caused because the source wasn't a directory + if e.errno == errno.ENOTDIR: + shutil.copy(src, dest) + else: + logger.error('Directory not copied. Error: {}'.format(e)) + + +logger = get_logger("gluu-common ") diff --git a/helm/pygluu/kubernetes/kubeapi.py b/helm/pygluu/kubernetes/kubeapi.py new file mode 100644 index 00000000000..9862436c7aa --- /dev/null +++ b/helm/pygluu/kubernetes/kubeapi.py @@ -0,0 +1,497 @@ +""" +pygluu.kubernetes.kubeapi +~~~~~~~~~~~~~~~~~~~~~~~~~ + + License terms and conditions for Gluu Cloud Native Edition: + https://www.apache.org/licenses/LICENSE-2.0 +""" + +import sys +import time + +from kubernetes import client, utils, config + +from pygluu.kubernetes.helpers import get_logger, check_microk8s_kube_config_file, exec_cmd +from pygluu.kubernetes.yamlparser import Parser + +logger = get_logger("gluu-kubernetes-api") + + +def load_kubernetes_config(mute=True): + """ + Loads kubernetes in cluster or from file configuration + :param mute: + """ + config_loaded = False + try: + config.load_incluster_config() + config_loaded = True + except config.config_exception.ConfigException: + if not mute: + logger.warning("Unable to load in-cluster configuration; trying to load from Kube config file") + try: + config.load_kube_config() + config_loaded = True + except (IOError, config.config_exception.ConfigException) as exc: + if not mute: + logger.warning("Unable to load Kube config; reason={}".format(exc)) + + if not config_loaded: + logger.error("Unable to load in-cluster or Kube config") + sys.exit(1) + + +class Kubernetes(object): + def __init__(self): + check_microk8s_kube_config_file() + load_kubernetes_config() + self.api_client = client.ApiClient() + self.custom_def_cli = client.CustomObjectsApi() + self.core_cli = client.CoreV1Api() + self.apps_cli = client.AppsV1Api() + self.rbac_cli = client.RbacAuthorizationV1Api() + self.extenstion_cli = client.ExtensionsV1beta1Api() + self.crd_cli = client.ApiextensionsV1beta1Api() + self.storage_cli = client.StorageV1Api() + self.admission_cli = client.AdmissionregistrationV1beta1Api() + self.delete_options = client.V1DeleteOptions() + self.delete_options.grace_period_seconds = 2 + self.delete_options.propagation_policy = 'Foreground' + self.core_cli.api_client.configuration.assert_hostname = False + self.apps_cli.api_client.configuration.assert_hostname = False + + @staticmethod + def check_error_and_response(starting_time, resp): + end_time = time.time() + running_time = end_time - starting_time + if resp.status != 404 and resp.status: + logger.info("Waiting for the kubernetes object to be fully terminated.") + time.sleep(1) + if running_time > 60: + logger.exception(resp) + return False + return True + else: + # The kubernetes object has been removed or does not exist" + return False + + @staticmethod + def check_create_error_and_response(e, kind, name): + """Checking create error """ + error = str(e) + if "AlreadyExists" in error or "409" in error: + logger.warning("Resource {}/{} already exists. Skipping...".format(kind, name)) + pass + elif "Unauthorized" in error or "401" in error: + logger.error("Unauthorized code status 401 while trying to create {}/{}.".format( + kind, name)) + pass + elif "Not Found" in error or "404" in error: + logger.error("Not found code status 404 while trying to create {}/{}. Trying again..".format( + kind, name)) + pass + else: + raise e + + @staticmethod + def check_read_error_and_response(starting_time, resp): + end_time = time.time() + running_time = end_time - starting_time + if resp.status == 404 and not resp.status: + logger.info("Resource not found. Trying to read again...") + time.sleep(1) + if running_time > 40: + logger.exception(resp) + return False + return True + else: + # The kubernetes object has been found" + return False + + def delete_validating_webhook_configuration(self, name): + """Delete validating webhook configuration with name""" + starting_time = time.time() + response = True + while response: + try: + resp = self.admission_cli.delete_validating_webhook_configuration(name, body=self.delete_options) + except client.rest.ApiException as e: + response = self.check_error_and_response(starting_time, e) + else: + response = self.check_error_and_response(starting_time, resp) + logger.info("validatingwebhookconfiguration/{} has been removed or does not exist".format(name)) + + def delete_mutating_webhook_configuration(self, name): + """Delete mutating webhook configuration with name""" + starting_time = time.time() + response = True + while response: + try: + resp = self.admission_cli.delete_mutating_webhook_configuration(name, body=self.delete_options) + except client.rest.ApiException as e: + response = self.check_error_and_response(starting_time, e) + else: + response = self.check_error_and_response(starting_time, resp) + logger.info("mutatingwebhookconfiguration/{} has been removed or does not exist".format(name)) + + def delete_service(self, name, namespace="default"): + """Delete service with name in namespace""" + starting_time = time.time() + response = True + while response: + try: + resp = self.core_cli.delete_namespaced_service(name=name, namespace=namespace, body=self.delete_options) + except client.rest.ApiException as e: + response = self.check_error_and_response(starting_time, e) + else: + response = self.check_error_and_response(starting_time, resp) + logger.info("service/{} from namespace/{} has been removed or does not exist".format(name, namespace)) + + def delete_deployment_using_name(self, name, namespace="default"): + """Delete deployment using name in namespace""" + starting_time = time.time() + response = True + while response: + try: + resp = self.apps_cli.delete_namespaced_deployment(name, namespace, body=self.delete_options) + except client.rest.ApiException as e: + response = self.check_error_and_response(starting_time, e) + else: + response = self.check_error_and_response(starting_time, resp) + logger.info('deployment/{} in namespace/{} has been removed or does not exist'.format(name, namespace)) + + def delete_secret(self, name, namespace="default"): + """Delete secret using name in namespace""" + starting_time = time.time() + response = True + while response: + try: + resp = self.core_cli.delete_namespaced_secret(name, namespace, body=self.delete_options) + except client.rest.ApiException as e: + response = self.check_error_and_response(starting_time, e) + else: + response = self.check_error_and_response(starting_time, resp) + logger.info('secret/{} from namespace/{} has been removed or does not exist'.format(name, namespace)) + + def delete_role(self, name, namespace="default"): + """Delete role using name in namespace""" + starting_time = time.time() + response = True + while response: + try: + resp = self.rbac_cli.delete_namespaced_role(name, namespace, body=self.delete_options) + except client.rest.ApiException as e: + response = self.check_error_and_response(starting_time, e) + else: + response = self.check_error_and_response(starting_time, resp) + logger.info('role/{} in namespace/{} has been removed or does not exist'.format(name, namespace)) + + def delete_role_binding(self, name, namespace="default"): + """Delete role binding using name in namespace""" + starting_time = time.time() + response = True + while response: + try: + resp = self.rbac_cli.delete_namespaced_role_binding(name, namespace, body=self.delete_options) + except client.rest.ApiException as e: + response = self.check_error_and_response(starting_time, e) + else: + response = self.check_error_and_response(starting_time, resp) + logger.info('rolebinding/{} from namespace/{} has been removed or does not exist'.format(name, namespace)) + + def delete_cluster_role(self, name): + """Delete cluster role using name""" + starting_time = time.time() + response = True + while response: + try: + resp = self.rbac_cli.delete_cluster_role(name, body=self.delete_options) + except client.rest.ApiException as e: + response = self.check_error_and_response(starting_time, e) + else: + response = self.check_error_and_response(starting_time, resp) + logger.info('role/{} has been removed or does not exist'.format(name)) + + def delete_cluster_role_binding(self, name): + """Delete cluster role binding using name""" + starting_time = time.time() + response = True + while response: + try: + resp = self.rbac_cli.delete_cluster_role_binding(name, body=self.delete_options) + except client.rest.ApiException as e: + response = self.check_error_and_response(starting_time, e) + else: + response = self.check_error_and_response(starting_time, resp) + logger.info('clusterrolebinding/{} has been removed or does not exist'.format(name)) + + def delete_service_account(self, name, namespace="default"): + """Delete service account using name in namespace""" + starting_time = time.time() + response = True + while response: + try: + resp = self.core_cli.delete_namespaced_service_account(name, namespace, body=self.delete_options) + except client.rest.ApiException as e: + response = self.check_error_and_response(starting_time, e) + else: + response = self.check_error_and_response(starting_time, resp) + logger.info('serviceaccount/{} in namespace/{} has been removed or does not exist'.format(name, namespace)) + + def delete_custom_resource(self, name): + """Delete custom resource using name""" + starting_time = time.time() + response = True + while response: + try: + resp = self.crd_cli.delete_custom_resource_definition(name, body=self.delete_options) + + except client.rest.ApiException as e: + response = self.check_error_and_response(starting_time, e) + else: + response = self.check_error_and_response(starting_time, resp) + logger.info('customeresource/{} has been removed or does not exist'.format(name)) + + def delete_storage_class(self, name): + """Delete storage class using name""" + starting_time = time.time() + response = True + while response: + try: + resp = self.storage_cli.delete_storage_class(name, body=self.delete_options) + except client.rest.ApiException as e: + response = self.check_error_and_response(starting_time, e) + else: + response = self.check_error_and_response(starting_time, resp) + logger.info('storageclass/{} has been removed or does not exist'.format(name)) + + def delete_namespaced_custom_object(self, filepath, group, version, plural, namespace="default"): + """Delete custom object using file in namespace""" + starting_time = time.time() + response = True + while response: + yaml_objects = Parser(filepath).return_manifests_dict + for manifest in yaml_objects: + try: + resp = self.custom_def_cli.delete_namespaced_custom_object(group=group, + version=version, + namespace=namespace, + plural=plural, + name=manifest["metadata"]["name"], + body=manifest) + + logger.info('Deleted {}/{} in namespace {}'.format(manifest["kind"], + manifest["metadata"]["name"], namespace)) + except client.rest.ApiException as e: + response = self.check_error_and_response(starting_time, e) + else: + response = self.check_error_and_response(starting_time, resp) + + def delete_namespaced_custom_object_by_name(self, group, version, plural, name, namespace="default"): + """Delete custom object using name in namespace""" + try: + resp = self.custom_def_cli.delete_namespaced_custom_object(group=group, + version=version, + namespace=namespace, + plural=plural, + name=name, + body=self.delete_options) + logger.info('Deleted {} in namespace {}'.format(name, namespace)) + except client.rest.ApiException as e: + if e.status == 404: + logger.info('{} in namespace {} not found.'.format(name, namespace)) + else: + logger.error(e, resp) + + def create_namespace(self, name, labels=None): + """Create namespace using name""" + labels = labels or {} + body = client.V1Secret() + metadata = client.V1ObjectMeta() + metadata.name = name + metadata.labels = labels + body.metadata = metadata + try: + self.core_cli.create_namespace(body=body, pretty="pretty") + logger.info('Created namespace {}'.format(name)) + return True + except client.rest.ApiException as e: + self.check_create_error_and_response(e, "Namespace", name) + return False + + def create_cluster_role_binding(self, cluster_role_binding_name, user_name, cluster_role_name): + """Create role binding using name=role_binding_name in namespace + connecting role_name using service_account_name""" + metadata = client.V1ObjectMeta(name=cluster_role_binding_name) + role = client.V1RoleRef(kind="ClusterRole", name=cluster_role_name, api_group="rbac.authorization.k8s.io") + subject = client.V1Subject(kind="User", name=user_name) + body = client.V1ClusterRoleBinding(subjects=[subject], metadata=metadata, role_ref=role) + + try: + self.rbac_cli.create_cluster_role_binding(body=body) + logger.info('Created cluster role binding {}'.format(cluster_role_binding_name)) + return True + except client.rest.ApiException as e: + self.check_create_error_and_response(e, "ClusterRoleBinding", cluster_role_binding_name) + return False + + def create_namespaced_custom_object(self, filepath, group, version, plural, namespace="default"): + """Create custom object using file in namespace""" + yaml_objects = Parser(filepath).return_manifests_dict + for manifest in yaml_objects: + try: + self.custom_def_cli.create_namespaced_custom_object(group=group, + version=version, + namespace=namespace, + plural=plural, + body=manifest) + + logger.info('Created {}/{} in namespace {}'.format(manifest["kind"], + manifest["metadata"]["name"], namespace)) + except (client.rest.ApiException, Exception) as e: + self.check_create_error_and_response(e, manifest["kind"], manifest["metadata"]["name"]) + + def patch_or_create_namespaced_secret(self, name, literal, value_of_literal, namespace="default", + secret_type="Opaque", second_literal=None, value_of_second_literal=None, + data=None): + """Patch secret and if not exist create""" + # Instantiate the Secret object + body = client.V1Secret() + metadata = client.V1ObjectMeta(name=name) + body.data = data + if not data: + body.data = {literal: value_of_literal} + body.metadata = metadata + body.type = secret_type + if second_literal: + body.data = {literal: value_of_literal, second_literal: value_of_second_literal} + try: + self.core_cli.patch_namespaced_secret(name, namespace, body) + logger.info('Secret {} in namespace {} has been patched'.format(name, namespace)) + return + except client.rest.ApiException as e: + if e.status == 404 or not e.status: + try: + self.core_cli.create_namespaced_secret(namespace=namespace, body=body) + logger.info('Created secret {} of type {} in namespace {}'.format(name, secret_type, namespace)) + return True + except client.rest.ApiException as e: + logger.exception(e) + return False + logger.exception(e) + return False + + def create_objects_from_dict(self, filepath, namespace=None): + """Create kubernetes object from a yaml encapsulated inside a dictionary""" + yaml_objects = Parser(filepath).return_manifests_dict + for manifest in yaml_objects: + try: + # handle special cases of namespace injection + if namespace: + manifest["metadata"]["namespace"] = namespace + utils.create_from_dict(self.api_client, manifest) + logger.info('Created {}/{}'.format(manifest["kind"], manifest["metadata"]["name"])) + except (client.rest.ApiException, Exception) as e: + # AttributeError: module 'kubernetes.client' has no attribute 'NetworkingIstioIoV1alpha3Api' + if "module 'kubernetes.client' has no attribute 'NetworkingIstioIoV1alpha3Api'" in str(e): + logger.warning("Creating {} failed.".format(manifest["kind"])) + logger.info("Trying again using kubectl...") + exec_cmd("kubectl apply -f {} -n {}".format(filepath, namespace)) + break + self.check_create_error_and_response(e, manifest["kind"], manifest["metadata"]["name"]) + + def list_pod_name_by_label(self, namespace="default", app_label=None): + """List pods names with app label in namespace""" + try: + pods_name = [] + response = self.core_cli.list_namespaced_pod(namespace=namespace, label_selector=app_label, watch=False) + number_of_pods = len(response.items) + for i in range(number_of_pods): + pods_name.append(response.items[i].metadata.name) + return pods_name + except client.rest.ApiException as e: + logger.exception(e) + + def read_namespaced_service(self, name, namespace="default"): + """Read service with name in namespace""" + starting_time = time.time() + response = True + while response: + try: + service = self.core_cli.read_namespaced_service(name=name, namespace=namespace) + logger.info('Reading service {}'.format(name)) + return service + except client.rest.ApiException as e: + response = self.check_read_error_and_response(starting_time, e) + + def read_namespaced_ingress(self, name, namespace="default"): + """Read service with name in namespace""" + starting_time = time.time() + response = True + while response: + try: + ingress = self.extenstion_cli.read_namespaced_ingress(name=name, namespace=namespace) + logger.info('Reading ingress {}'.format(name)) + return ingress + except client.rest.ApiException as e: + response = self.check_read_error_and_response(starting_time, e) + + def read_namespaced_pod_status(self, name, timeout, namespace="default"): + """Read pod status with name in namespace""" + starting_time = time.time() + try: + finished_prep_boolean = False + while not finished_prep_boolean: + end_time = time.time() + running_time = end_time - starting_time + time.sleep(5) + response = self.core_cli.read_namespaced_pod_status(name=name, namespace=namespace) + all_statuses = response.status.conditions + try: + for status in all_statuses: + if status.type == "Ready": + try: + check_if_job = response.metadata.labels["job-name"] + except KeyError: + check_if_job = None + if check_if_job and status.reason == "PodCompleted": + finished_prep_boolean = True + break + elif not check_if_job and status.status == "True": + finished_prep_boolean = True + break + except TypeError: + logger.warning("Pod might not exist or was evicted.") + if running_time > timeout: + logger.warning("Timeout exceeded. This may not be an error. Please check pods statuses.") + return False + logger.info("Waiting for pod {} to get ready".format(name)) + except client.rest.ApiException as e: + logger.exception(e) + + def check_pods_statuses(self, namespace="default", app_label=None, timeout=300): + """Loop through pod names and check statuses""" + time.sleep(10) + pods_name = self.list_pod_name_by_label(namespace, app_label) + for pod_name in pods_name: + self.read_namespaced_pod_status(name=pod_name, namespace=namespace, timeout=timeout) + + def list_nodes(self): + """List all nodes""" + try: + nodes_list = self.core_cli.list_node(pretty="pretty") + logger.info("Getting list of nodes") + return nodes_list + except client.rest.ApiException as e: + logger.exception(e) + return False + + def read_node(self, name): + """Read node information""" + try: + node_data = self.core_cli.read_node(name) + logger.info("Getting node {} data".format(name)) + return node_data + except client.rest.ApiException as e: + logger.exception(e) + return False diff --git a/helm/pygluu/kubernetes/mysql.py b/helm/pygluu/kubernetes/mysql.py new file mode 100644 index 00000000000..50badab2eef --- /dev/null +++ b/helm/pygluu/kubernetes/mysql.py @@ -0,0 +1,49 @@ +""" +pygluu.kubernetes.mysql +~~~~~~~~~~~~~~~~~~~~~~~ + License terms and conditions for Gluu Cloud Native Edition: + https://www.apache.org/licenses/LICENSE-2.0 + Handles MySQL operations +""" + +from pygluu.kubernetes.helpers import get_logger, exec_cmd +from pygluu.kubernetes.kubeapi import Kubernetes +from pygluu.kubernetes.settings import ValuesHandler + +logger = get_logger("gluu-mysql ") + + +class MySQL(object): + def __init__(self): + self.settings = ValuesHandler() + self.kubernetes = Kubernetes() + self.timeout = 120 + + def install_mysql(self): + self.uninstall_mysql() + self.kubernetes.create_namespace(name=self.settings.get("installer-settings.sql.namespace"), + labels={"app": "mysql"}) + + exec_cmd("helm repo add bitnami https://charts.bitnami.com/bitnami") + exec_cmd("helm repo update") + exec_cmd("helm install {} bitnami/mysql " + "--set auth.rootPassword={} " + "--set auth.database={} " + "--set auth.username={} " + "--set auth.password={} " + "--namespace={} ".format("gluu", + self.settings.get("config.configmap.cnSqldbUserPassword"), + self.settings.get("config.configmap.cnSqlDbName"), + self.settings.get("config.configmap.cnSqlDbUser"), + self.settings.get("config.configmap.cnSqldbUserPassword"), + self.settings.get("installer-settings.sql.namespace"))) + + if not self.settings.get("installer-settings.aws.lbType") == "alb": + self.kubernetes.check_pods_statuses(self.settings.get("installer-settings.sql.namespace"), "app=mysql", + self.timeout) + + def uninstall_mysql(self): + logger.info("Removing gluu-mysql...") + logger.info("Removing mysql...") + exec_cmd("helm delete {} --namespace={}".format("gluu", + self.settings.get("installer-settings.sql.namespace"))) diff --git a/helm/pygluu/kubernetes/postgres.py b/helm/pygluu/kubernetes/postgres.py new file mode 100644 index 00000000000..bd4c73f7240 --- /dev/null +++ b/helm/pygluu/kubernetes/postgres.py @@ -0,0 +1,70 @@ +""" +pygluu.kubernetes.postgres +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + License terms and conditions for Gluu Cloud Native Edition: + https://www.apache.org/licenses/LICENSE-2.0 + Handles Postgres operations +""" + +from pygluu.kubernetes.helpers import get_logger, exec_cmd +from pygluu.kubernetes.kubeapi import Kubernetes +from pygluu.kubernetes.settings import ValuesHandler + +logger = get_logger("gluu-postgres ") + + +class Postgres(object): + def __init__(self): + self.settings = ValuesHandler() + self.kubernetes = Kubernetes() + self.timeout = 120 + + def install_postgres(self): + self.uninstall_postgres() + if self.settings.get("installer-settings.jackrabbit.clusterMode") == "Y": + self.kubernetes.create_namespace( + name=f'jackrabbit{self.settings.get("installer-settings.postgres.namespace")}', + labels={"app": "postgres"}) + exec_cmd("helm repo add bitnami https://charts.bitnami.com/bitnami") + exec_cmd("helm repo update") + exec_cmd("helm install {} bitnami/postgresql " + "--set global.postgresql.postgresqlDatabase={} " + "--set global.postgresql.postgresqlPassword={} " + "--set global.postgresql.postgresqlUsername={} " + "--namespace=jackrabbit{}".format("postgresql", + self.settings.get( + "config.configmap.cnJackrabbitPostgresDatabaseName"), + self.settings.get( + "jackrabbit.secrets.cnJackrabbitPostgresPassword"), + self.settings.get("config.configmap.cnJackrabbitPostgresUser"), + self.settings.get("installer-settings.postgres.namespace"))) + + if self.settings.get("global.cnPersistenceType") == "sql" and \ + self.settings.get("config.configmap.cnSqlDbDialect") == "pgsql": + self.kubernetes.create_namespace(name=self.settings.get("installer-settings.postgres.namespace"), + labels={"app": "mysql"}) + exec_cmd("helm install {} bitnami/postgresql " + "--set global.postgresql.postgresqlDatabase={} " + "--set global.postgresql.postgresqlPassword={} " + "--set global.postgresql.postgresqlUsername={} " + "--namespace={}".format("gluu", + self.settings.get("config.configmap.cnSqlDbName"), + self.settings.get("config.configmap.cnSqldbUserPassword"), + self.settings.get("config.configmap.cnSqlDbUser"), + self.settings.get("installer-settings.postgres.namespace"))) + + if not self.settings.get("installer-settings.aws.lbType") == "alb": + self.kubernetes.check_pods_statuses(self.settings.get("POSTGRES_NAMESPACE"), "app=postgres", + self.timeout) + + def uninstall_postgres(self): + logger.info("Removing gluu-postgres...") + logger.info("Removing postgres...") + exec_cmd("helm delete {} --namespace=jackrabbit{}".format("sql", + self.settings.get( + "installer-settings.postgres.namespace"))) + if self.settings.get("global.cnPersistenceType") == "sql" and \ + self.settings.get("config.configmap.cnSqlDbDialect") == "pgsql": + exec_cmd("helm delete {} --namespace={}".format("gluu", + self.settings.get("installer-settings.postgres.namespace"))) diff --git a/helm/pygluu/kubernetes/pycert.py b/helm/pygluu/kubernetes/pycert.py new file mode 100644 index 00000000000..1851752b8fa --- /dev/null +++ b/helm/pygluu/kubernetes/pycert.py @@ -0,0 +1,181 @@ +""" +pygluu.kubernetes.pycert +~~~~~~~~~~~~~~~~~~~~~~~~ + + License terms and conditions for Gluu Cloud Native Edition: + https://www.apache.org/licenses/LICENSE-2.0 + Generate certificate authority cert, key and chain cert and key signed by CA generated. +""" + +import datetime +import OpenSSL.crypto +import OpenSSL.SSL +from pygluu.kubernetes.helpers import get_logger +from cryptography import x509 +from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa + +logger = get_logger("gluu-cert-manager ") + + +def setup_crts(ca_common_name, cert_common_name, san_list, + ca_cert_file="./ca.crt", + ca_key_file="./ca.key", + cert_file="./chain.pem", + key_file="./pkey.key"): + """ + Generate certificate authority cert, key and chain cert and key signed by CA generated. + + :param ca_common_name: + :param cert_common_name: + :param san_list: + :param ca_cert_file: + :param ca_key_file: + :param cert_file: + :param key_file: + """ + logger.info("Generating CA private key") + root_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + backend=default_backend() + ) + subject = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, ca_common_name), + ]) + issuer = [ + x509.DirectoryName(x509.Name([ + x509.NameAttribute(x509.OID_COMMON_NAME, ca_common_name), + ])) + ] + skid = x509.SubjectKeyIdentifier.from_public_key( + root_key.public_key()) + root_serial_number = x509.random_serial_number() + logger.info("Building CA certificate") + root_cert = x509.CertificateBuilder( + ).subject_name(subject).issuer_name( + subject).public_key(root_key.public_key()).serial_number( + root_serial_number).not_valid_before( + datetime.datetime.utcnow()).not_valid_after( + datetime.datetime.utcnow() + datetime.timedelta(days=3650)).add_extension( + x509.BasicConstraints(ca=True, path_length=None), critical=False, + ).add_extension( + x509.KeyUsage( + digital_signature=False, + key_encipherment=False, + content_commitment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=True, + encipher_only=False, + decipher_only=False + ), + critical=False + + ).add_extension( + skid, + critical=False + + ).add_extension( + x509.AuthorityKeyIdentifier( + key_identifier=skid.digest, + authority_cert_issuer=issuer, + authority_cert_serial_number=root_serial_number + ), + critical=False + + ).sign(root_key, hashes.SHA256(), default_backend()) + + logger.info("Building {} certificate signed by CA".format(cert_common_name)) + # Generate cert for CA + cert_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) + new_subject = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, cert_common_name), + ]) + x509_sans = [] + for san in san_list: + x509_sans.append(x509.DNSName(san)) + cert = x509.CertificateBuilder().subject_name( + new_subject + ).issuer_name( + root_cert.subject + ).public_key( + cert_key.public_key() + ).serial_number( + x509.random_serial_number() + ).not_valid_before( + datetime.datetime.utcnow() + ).not_valid_after( + datetime.datetime.utcnow() + datetime.timedelta(days=365) + ).add_extension( + x509.SubjectAlternativeName(x509_sans), + critical=False, + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), critical=False, + ).add_extension( + x509.ExtendedKeyUsage([ + ExtendedKeyUsageOID.SERVER_AUTH, + ]), critical=False, + ).add_extension( + x509.KeyUsage( + digital_signature=True, + key_encipherment=True, + content_commitment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False + ), + critical=False + + ).add_extension( + x509.SubjectKeyIdentifier.from_public_key( + cert_key.public_key() + ), + critical=False + + ).add_extension( + x509.AuthorityKeyIdentifier( + key_identifier=skid.digest, + authority_cert_issuer=issuer, + authority_cert_serial_number=root_serial_number + ), critical=False + + ).sign(root_key, hashes.SHA256(), default_backend()) + # Dump to scratch + ca_cert = root_cert.public_bytes(encoding=serialization.Encoding.PEM) + ca_key = root_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + # Return PEM + cert_pem = cert.public_bytes(encoding=serialization.Encoding.PEM) + + cert_key_pem = cert_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + crt = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, + cert_pem) + crt_header = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_TEXT, crt) + logger.info("Dumping {}".format(ca_cert_file)) + with open(ca_cert_file, "wb") as f: + f.write(ca_cert) + logger.info("Dumping {}".format(ca_key_file)) + with open(ca_key_file, "wb") as f: + f.write(ca_key) + logger.info("Dumping {}".format(cert_file)) + with open(cert_file, "wb") as f: + f.write(crt_header + cert_pem) + logger.info("Dumping {}".format(key_file)) + with open(key_file, "wb") as f: + f.write(cert_key_pem) diff --git a/helm/pygluu/kubernetes/redis.py b/helm/pygluu/kubernetes/redis.py new file mode 100644 index 00000000000..cc7f526b381 --- /dev/null +++ b/helm/pygluu/kubernetes/redis.py @@ -0,0 +1,54 @@ +""" +pygluu.kubernetes.redis +~~~~~~~~~~~~~~~~~~~~~~~ + + License terms and conditions for Gluu Cloud Native Edition: + https://www.apache.org/licenses/LICENSE-2.0 + Handles Redis installation for testing. +""" + +from pygluu.kubernetes.helpers import get_logger, exec_cmd +from pygluu.kubernetes.kubeapi import Kubernetes +from pygluu.kubernetes.settings import ValuesHandler + + +logger = get_logger("gluu-redis ") + + +class Redis(object): + def __init__(self): + self.settings = ValuesHandler() + self.kubernetes = Kubernetes() + self.timeout = 120 + if "gke" in self.settings.get("installer-settings.volumeProvisionStrategy"): + user_account, stderr, retcode = exec_cmd("gcloud config get-value core/account") + user_account = str(user_account, "utf-8").strip() + + user, stderr, retcode = exec_cmd("whoami") + user = str(user, "utf-8").strip() + cluster_role_binding_name = "cluster-admin-{}".format(user) + self.kubernetes.create_cluster_role_binding(cluster_role_binding_name=cluster_role_binding_name, + user_name=user_account, + cluster_role_name="cluster-admin") + + def install_redis(self): + self.uninstall_redis() + self.kubernetes.create_namespace(name=self.settings.get("installer-settings.redis.namespace"), + labels={"app": "redis"}) + exec_cmd("helm repo add bitnami https://charts.bitnami.com/bitnami") + exec_cmd("helm repo update") + exec_cmd("helm install {} bitnami/redis-cluster " + "--set global.redis.password={} " + "--namespace={}".format("redis-cluster", + self.settings.get("config.redisPassword"), + self.settings.get("installer-settings.redis.namespace"))) + + if not self.settings.get("installer-settings.aws.lbType") == "alb": + self.kubernetes.check_pods_statuses(self.settings.get("installer-settings.namespace"), "app=redis-cluster", + self.timeout) + + def uninstall_redis(self): + logger.info("Removing gluu-redis-cluster...") + logger.info("Removing redis...") + exec_cmd("helm delete {} --namespace={}".format("redis-cluster", + self.settings.get("installer-settings.redis.namespace"))) diff --git a/helm/pygluu/kubernetes/settings.py b/helm/pygluu/kubernetes/settings.py new file mode 100644 index 00000000000..a0f7b6610c9 --- /dev/null +++ b/helm/pygluu/kubernetes/settings.py @@ -0,0 +1,128 @@ +""" +pygluu.kubernetes.settings +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with settings saved in a dictionary for terminal and GUI installations. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import contextlib +import os +import shutil +from dotty_dict import dotty +from pygluu.kubernetes.yamlparser import Parser +from pathlib import Path +from pygluu.kubernetes.helpers import get_logger + +logger = get_logger("gluu-values-yaml ") + + +def unlink_values_yaml(): + filename = Path("./helm/gluu/values.yaml") + with contextlib.suppress(FileNotFoundError): + os.unlink(filename) + + +def iterate_dict(dictionary, key_value=""): + for k, v in dictionary.items(): + if isinstance(v, dict): + iterate_dict(v) + else: + dictionary[k] = key_value + + +class ValuesHandler(object): + def __init__(self, values_file="./helm/gluu/override-values.yaml", + values_schema_file="./helm/gluu/values.schema.json"): + self.values_file = Path(values_file) + self.values_schema = Path(values_schema_file) + self.errors = list() + self.values_file_parser = Parser(self.values_file, True) + self.schema = {} + + def load(self): + """ + Get merged settings (default and custom settings from json file). + """ + # Check if running in container and settings.json mounted + try: + shutil.copy(Path("./override-values.yaml"), self.values_file) + self.values_file_parser = Parser(self.values_file, True) + except FileNotFoundError: + # No installation settings mounted as /override-values.yaml. Checking values.yaml. + pass + + def store_override_file(self): + """ + Copy override file to main directory + """ + shutil.copy(Path("./helm/gluu/override-values.yaml"), Path("./override-values.yaml")) + + def store_data(self, clean_data=False): + try: + self.values_file_parser.dump_it(clean_data) + return True + except Exception as exc: + logger.info(f"Uncaught error={exc}") + return False + + def set(self, keys_string, value): + """ + single update + """ + try: + dot = dotty(self.values_file_parser) + dot[keys_string] = value + self.store_data() + except Exception as exc: + logger.info(f"Uncaught error={exc}") + return False + + def get(self, keys_string): + """ + This method receives a dict and list of attributes to return the innermost value of the give dict + """ + try: + dot = dotty(self.values_file_parser) + return dot[keys_string] + + except (KeyError, NameError): + logger.info("No Value Can Be Found for " + str(keys_string)) + return False + + def update(self, collection): + """ + mass update + """ + try: + self.values_file_parser.update(collection) + self.store_data() + return True + except Exception as exc: + logger.info(f"Uncaught error={exc}") + return False + + def reset_data(self): + """ + reset values.yaml to default_settings + """ + try: + iterate_dict(self.values_file_parser) + self.store_data() + return True + except Exception as exc: + logger.info(f"Uncaught error={exc}") + return False + + def remove_empty_keys(self): + """ + removes empty keys for override-values.yaml + """ + try: + self.store_data(clean_data=True) + self.store_override_file() + return True + except Exception as exc: + logger.error(f"Uncaught error={exc}") + return False diff --git a/helm/pygluu/kubernetes/templates/LICENSE b/helm/pygluu/kubernetes/templates/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/helm/pygluu/kubernetes/templates/alb/ingress.yaml b/helm/pygluu/kubernetes/templates/alb/ingress.yaml new file mode 100644 index 00000000000..a9c16b4373e --- /dev/null +++ b/helm/pygluu/kubernetes/templates/alb/ingress.yaml @@ -0,0 +1,117 @@ +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: gluu + annotations: + kubernetes.io/ingress.class: alb + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/certificate-arn: "" + alb.ingress.kubernetes.io/auth-session-cookie: custom-cookie + alb.ingress.kubernetes.io/auth-session-timeout: '3600' + alb.ingress.kubernetes.io/target-group-attributes: stickiness.enabled=true,stickiness.lb_cookie.duration_seconds=60,stickiness.type=lb_cookie + alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '10' + alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]' + alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}' + alb.ingress.kubernetes.io/actions.scim-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "Path":"/jans-scim/restv1/scim-configuration", "StatusCode": "HTTP_301"}}' + alb.ingress.kubernetes.io/actions.openid-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "Path":"/jans-auth/.well-known/openid-configuration", "StatusCode": "HTTP_301"}}' + alb.ingress.kubernetes.io/actions.uma-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "Path":"/jans-auth/restv1/uma2-configuration", "StatusCode": "HTTP_301"}}' + alb.ingress.kubernetes.io/actions.webfinger-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "Path":"/jans-auth/.well-known/webfinger", "StatusCode": "HTTP_301"}}' + alb.ingress.kubernetes.io/actions.simple-web-discovery-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "Path":"/jans-auth/.well-known/simple-web-discovery", "StatusCode": "HTTP_301"}}' + alb.ingress.kubernetes.io/actions.fido-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "Path":"/jans-auth/restv1/fido-configuration", "StatusCode": "HTTP_301"}}' + alb.ingress.kubernetes.io/actions.fido2-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "Path":"/jans-fido2/restv1/configuration", "StatusCode": "HTTP_301"}}' + alb.ingress.kubernetes.io/actions.main-page-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "Path":"/identity/", "StatusCode": "HTTP_301"}}' + + +spec: + rules: + - host: FQDN + http: + paths: + - path: /* + backend: + serviceName: ssl-redirect + servicePort: use-annotation + - path: /.well-known/scim-configuration + backend: + serviceName: scim-redirect + servicePort: use-annotation + - path: /.well-known/openid-configuration + backend: + serviceName: openid-redirect + servicePort: use-annotation + - path: /.well-known/uma2-configuration + backend: + serviceName: uma-redirect + servicePort: use-annotation + - path: /.well-known/webfinger + backend: + serviceName: webfinger-redirect + servicePort: use-annotation + - path: /.well-known/simple-web-discovery + backend: + serviceName: simple-web-discovery-redirect + servicePort: use-annotation + - path: /.well-known/fido-configuration + backend: + serviceName: fido-redirect + servicePort: use-annotation + - path: /.well-known/fido2-configuration + backend: + serviceName: fido2-redirect + servicePort: use-annotation + - path: / + backend: + serviceName: main-page-redirect + servicePort: use-annotation + - path: /.well-known/scim-configuration + backend: + serviceName: scim + servicePort: 8080 + - path: /.well-known/openid-configuration + backend: + serviceName: auth-server + servicePort: 8080 + - path: /.well-known/uma2-configuration + backend: + serviceName: auth-server + servicePort: 8080 + - path: /.well-known/webfinger + backend: + serviceName: auth-server + servicePort: 8080 + - path: /.well-known/simple-web-discovery + backend: + serviceName: auth-server + servicePort: 8080 + - path: /.well-known/fido-configuration + backend: + serviceName: auth-server + servicePort: 8080 + - path: /.well-known/fido2-configuration + backend: + serviceName: auth-server + servicePort: 8080 + - path: /jans-scim* + backend: + serviceName: jans-scim + servicePort: 8080 + - path: /jans-config-api* + backend: + serviceName: config-api + servicePort: 8074 + - path: /auth-server* + backend: + serviceName: auth-server + servicePort: 8080 + - path: /idp* + backend: + serviceName: oxshibboleth + servicePort: 8080 + - path: /passport* + backend: + serviceName: oxpassport + servicePort: 8090 + - path: /casa* + backend: + serviceName: casa + servicePort: 8080 \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/couchbase/backup/couchbase-backup.yaml b/helm/pygluu/kubernetes/templates/couchbase/backup/couchbase-backup.yaml new file mode 100644 index 00000000000..7fbeffb4b72 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/couchbase/backup/couchbase-backup.yaml @@ -0,0 +1,18 @@ +apiVersion: couchbase.com/v2 +kind: CouchbaseBackup +metadata: + name: couchbase-gluu + labels: + cluster: gluu-couchbase +spec: + strategy: full_incremental + full: + schedule: "0 2 * * 6" + incremental: + schedule: "*/30 * * * *" + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 3 + backOffLimit: 2 + backupRetention: 24h + logRetention: 168h + size: 5Gi diff --git a/helm/pygluu/kubernetes/templates/couchbase/couchbase-buckets.yaml b/helm/pygluu/kubernetes/templates/couchbase/couchbase-buckets.yaml new file mode 100644 index 00000000000..12528f08fbc --- /dev/null +++ b/helm/pygluu/kubernetes/templates/couchbase/couchbase-buckets.yaml @@ -0,0 +1,50 @@ +apiVersion: couchbase.com/v2 +kind: CouchbaseBucket +metadata: + name: jans + labels: + cluster: jans-couchbase +spec: + name: jans #DO NOT CHANGE THIS LINE + memoryQuota: 100Mi + replicas: 1 + ioPriority: low + evictionPolicy: valueOnly + conflictResolution: seqno + enableFlush: true + enableIndexReplica: false + compressionMode: passive +--- +apiVersion: couchbase.com/v2 +kind: CouchbaseBucket +metadata: + name: jans-site + labels: + cluster: jans-couchbase +spec: + name: jans_site #DO NOT CHANGE THIS LINE + memoryQuota: 100Mi + replicas: 1 + ioPriority: low + evictionPolicy: valueOnly + conflictResolution: seqno + enableFlush: true + enableIndexReplica: false + compressionMode: passive +--- +apiVersion: couchbase.com/v2 +kind: CouchbaseBucket +metadata: + name: jans-user + labels: + cluster: jans-couchbase +spec: + name: jans_user #DO NOT CHANGE THIS LINE + memoryQuota: 100Mi + replicas: 1 + ioPriority: high + evictionPolicy: valueOnly + conflictResolution: seqno + enableFlush: true + enableIndexReplica: false + compressionMode: passive diff --git a/helm/pygluu/kubernetes/templates/couchbase/couchbase-cluster.yaml b/helm/pygluu/kubernetes/templates/couchbase/couchbase-cluster.yaml new file mode 100644 index 00000000000..c60ae7ec91d --- /dev/null +++ b/helm/pygluu/kubernetes/templates/couchbase/couchbase-cluster.yaml @@ -0,0 +1,173 @@ +apiVersion: couchbase.com/v2 +kind: CouchbaseCluster +metadata: + name: cbjans +spec: + image: couchbase/server:6.6.0 + antiAffinity: false + networking: + tls: + static: + serverSecret: couchbase-server-tls + operatorSecret: couchbase-operator-tls + security: + adminSecret: cb-auth + rbac: + managed: true + selector: + matchLabels: + cluster: cbjans + exposeAdminConsole: true + adminConsoleServices: + - data + exposedFeatures: + - xdcr + - client + cluster: + autoCompaction: + databaseFragmentationThreshold: + percent: 30 + size: 1Gi + viewFragmentationThreshold: + percent: 30 + size: 1Gi + parallelCompaction: false + timeWindow: + start: 02:00 + end: 06:00 + abortCompactionOutsideWindow: true + tombstonePurgeInterval: 72h + dataServiceMemoryQuota: 4024Mi + indexServiceMemoryQuota: 1024Mi + searchServiceMemoryQuota: 1024Mi + eventingServiceMemoryQuota: 1024Mi + analyticsServiceMemoryQuota: 1024Mi + autoFailoverTimeout: 10s + autoFailoverMaxCount: 3 + autoFailoverOnDataDiskIssues: true + autoFailoverOnDataDiskIssuesTimePeriod: 120s + autoFailoverServerGroup: false + buckets: + managed: true + selector: + matchLabels: + cluster: jans-couchbase + servers: + - name: analytics-us-west-2a + size: 1 + services: + - search + - analytics + - eventing + serverGroups: + - us-west-2a + volumeMounts: + default: pvc-general + analytics: + - pvc-analytics + - name: analytics-us-west-2b + size: 1 + services: + - search + - analytics + - eventing + serverGroups: + - us-west-2b + volumeMounts: + default: pvc-general + analytics: + - pvc-analytics + - name: data-us-west-2c + size: 1 + services: + - data + serverGroups: + - us-west-2c + volumeMounts: + default: pvc-general + data: pvc-data + - name: data-us-west-2b + size: 1 + services: + - data + serverGroups: + - us-west-2b + volumeMounts: + default: pvc-general + data: pvc-data + - name: index-us-west-2a + size: 1 + services: + - query + - index + serverGroups: + - us-west-2a + volumeMounts: + default: pvc-general + index: pvc-index + - name: index-us-west-2c + size: 1 + services: + - query + - index + serverGroups: + - us-west-2c + volumeMounts: + default: pvc-general + index: pvc-index + - name: query-us-west-2a + size: 1 + services: + - query + serverGroups: + - us-west-2a + volumeMounts: + default: pvc-general + query: pvc-query + - name: query-us-west-2c + size: 1 + services: + - query + serverGroups: + - us-west-2c + volumeMounts: + default: pvc-general + query: pvc-query + securityContext: + fsGroup: 1000 + volumeClaimTemplates: + - metadata: + name: pvc-general + spec: + storageClassName: couchbase-sc + resources: + requests: + storage: 10Gi + - metadata: + name: pvc-data + spec: + storageClassName: couchbase-sc + resources: + requests: + storage: 10Gi + - metadata: + name: pvc-index + spec: + storageClassName: couchbase-sc + resources: + requests: + storage: 10Gi + - metadata: + name: pvc-query + spec: + storageClassName: couchbase-sc + resources: + requests: + storage: 10Gi + - metadata: + name: pvc-analytics + spec: + storageClassName: couchbase-sc + resources: + requests: + storage: 10Gi \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/couchbase/couchbase-ephemeral-buckets.yaml b/helm/pygluu/kubernetes/templates/couchbase/couchbase-ephemeral-buckets.yaml new file mode 100644 index 00000000000..f598fd3e451 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/couchbase/couchbase-ephemeral-buckets.yaml @@ -0,0 +1,47 @@ +apiVersion: couchbase.com/v2 +kind: CouchbaseEphemeralBucket +metadata: + name: jans-cache + labels: + cluster: jans-couchbase +spec: + name: jans_cache + memoryQuota: 100Mi + replicas: 1 + ioPriority: high + evictionPolicy: nruEviction + conflictResolution: seqno + enableFlush: true + compressionMode: passive +--- +apiVersion: couchbase.com/v2 +kind: CouchbaseEphemeralBucket +metadata: + name: jans-token + labels: + cluster: jans-couchbase +spec: + name: jans_token + memoryQuota: 100Mi + replicas: 1 + ioPriority: high + evictionPolicy: nruEviction + conflictResolution: seqno + enableFlush: true + compressionMode: passive +--- +apiVersion: couchbase.com/v2 +kind: CouchbaseEphemeralBucket +metadata: + name: jans-session + labels: + cluster: jans-couchbase +spec: + name: jans_session + memoryQuota: 100Mi + replicas: 1 + ioPriority: high + evictionPolicy: nruEviction + conflictResolution: seqno + enableFlush: true + compressionMode: passive \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/couchbase/couchbase-group.yaml b/helm/pygluu/kubernetes/templates/couchbase/couchbase-group.yaml new file mode 100644 index 00000000000..87a301d26e5 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/couchbase/couchbase-group.yaml @@ -0,0 +1,59 @@ +apiVersion: couchbase.com/v2 +kind: CouchbaseGroup +metadata: + name: jans-group + labels: + cluster: cbjans +spec: + roles: + - name: query_select + bucket: jans + - name: query_select + bucket: jans_site + - name: query_select + bucket: jans_user + - name: query_select + bucket: jans_cache + - name: query_select + bucket: jans_token + - name: query_select + bucket: jans_session + + - name: query_update + bucket: jans + - name: query_update + bucket: jans_site + - name: query_update + bucket: jans_user + - name: query_update + bucket: jans_cache + - name: query_update + bucket: jans_token + - name: query_update + bucket: jans_session + + - name: query_insert + bucket: jans + - name: query_insert + bucket: jans_site + - name: query_insert + bucket: jans_user + - name: query_insert + bucket: jans_cache + - name: query_insert + bucket: jans_token + - name: query_insert + bucket: jans_session + + - name: query_delete + bucket: jans + - name: query_delete + bucket: jans_site + - name: query_delete + bucket: jans_user + - name: query_delete + bucket: jans_cache + - name: query_delete + bucket: jans_token + - name: query_delete + bucket: jans_session diff --git a/helm/pygluu/kubernetes/templates/couchbase/couchbase-rolebinding.yaml b/helm/pygluu/kubernetes/templates/couchbase/couchbase-rolebinding.yaml new file mode 100644 index 00000000000..c627d02a69a --- /dev/null +++ b/helm/pygluu/kubernetes/templates/couchbase/couchbase-rolebinding.yaml @@ -0,0 +1,11 @@ +apiVersion: couchbase.com/v2 +kind: CouchbaseRoleBinding +metadata: + name: jans-role-binding +spec: + subjects: + - kind: CouchbaseUser + name: jans + roleRef: + kind: CouchbaseGroup + name: jans-group diff --git a/helm/pygluu/kubernetes/templates/couchbase/couchbase-user.yaml b/helm/pygluu/kubernetes/templates/couchbase/couchbase-user.yaml new file mode 100644 index 00000000000..7b790d99748 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/couchbase/couchbase-user.yaml @@ -0,0 +1,10 @@ +apiVersion: couchbase.com/v2 +kind: CouchbaseUser +metadata: + name: jans + labels: + cluster: cbjans +spec: + fullName: "Janssen Cloud Native" + authDomain: local + authSecret: jans-couchbase-user-password \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/couchbase/storageclasses.yaml b/helm/pygluu/kubernetes/templates/couchbase/storageclasses.yaml new file mode 100644 index 00000000000..a6a446fc4fd --- /dev/null +++ b/helm/pygluu/kubernetes/templates/couchbase/storageclasses.yaml @@ -0,0 +1,15 @@ +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: couchbase-sc + annotations: + storageclass.beta.kubernetes.io/is-default-class: "false" +provisioner: kubernetes.io/aws-ebs +allowVolumeExpansion: true +volumeBindingMode: WaitForFirstConsumer +reclaimPolicy: Retain +mountOptions: +- debug +parameters: + type: VOLUMETYPE + encrypted: "true" \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/gluu_versions.json b/helm/pygluu/kubernetes/templates/gluu_versions.json new file mode 100644 index 00000000000..9d6d56888ba --- /dev/null +++ b/helm/pygluu/kubernetes/templates/gluu_versions.json @@ -0,0 +1,62 @@ +{ + "5.0": { + "casa.image.repository": "gluufederation/casa", + "casa.image.tag": "5.0.0_dev", + "fido2.image.repository": "janssenproject/fido2", + "fido2.image.tag": "1.0.0-beta.14", + "scim.image.repository": "janssenproject/scim", + "scim.image.tag": "1.0.0-beta.14", + "config.image.repository": "janssenproject/configurator", + "config.image.tag": "1.0.0-beta.14", + "config-api.image.repository": "janssenproject/config-api", + "config-api.image.tag": "1.0.0-beta.14", + "auth-server-key-rotation.image.repository": "janssenproject/certmanager", + "auth-server-key-rotation.image.tag": "1.0.0-beta.14", + "opendj.image.repository": "gluufederation/opendj", + "opendj.image.tag": "5.0.0_dev", + "jackrabbit.image.repository": "gluufederation/jackrabbit", + "jackrabbit.image.tag": "4.3.0_dev", + "auth-server.image.repository": "janssenproject/auth-server", + "auth-server.image.tag": "1.0.0-beta.14", + "client-api.image.repository": "janssenproject/client-api", + "client-api.image.tag": "1.0.0-beta.14", + "oxpassport.image.repository": "gluufederation/oxpassport", + "oxpassport.image.tag": "4.3.0_dev", + "oxshibboleth.image.repository": "gluufederation/oxshibboleth", + "oxshibboleth.image.tag": "4.3.0_dev", + "persistence.image.repository": "janssenproject/persistence-loader", + "persistence.image.tag": "1.0.0-beta.14", + "installer-settings.upgrade.image.repository": "gluufederation/upgrade", + "installer-settings.upgrade.image.tag": "4.3.0_dev" + }, + "5.0.0_dev": { + "casa.image.repository": "gluufederation/casa", + "casa.image.tag": "4.3.0_dev", + "fido2.image.repository": "janssenproject/fido2", + "fido2.image.tag": "1.0.0_dev", + "scim.image.repository": "janssenproject/scim", + "scim.image.tag": "1.0.0_dev", + "config.image.repository": "janssenproject/configurator", + "config.image.tag": "1.0.0_dev", + "config-api.image.repository": "janssenproject/config-api", + "config-api.image.tag": "1.0.0_dev", + "auth-server-key-rotation.image.repository": "janssenproject/certmanager", + "auth-server-key-rotation.image.tag": "1.0.0_dev", + "opendj.image.repository": "gluufederation/opendj", + "opendj.image.tag": "5.0.0_dev", + "jackrabbit.image.repository": "gluufederation/jackrabbit", + "jackrabbit.image.tag": "4.3.0_dev", + "auth-server.image.repository": "janssenproject/auth-server", + "auth-server.image.tag": "1.0.0_dev", + "client-api.image.repository": "janssenproject/client-api", + "client-api.image.tag": "1.0.0_dev", + "oxpassport.image.repository": "gluufederation/oxpassport", + "oxpassport.image.tag": "4.3.0_dev", + "oxshibboleth.image.repository": "gluufederation/oxshibboleth", + "oxshibboleth.image.tag": "4.3.0_dev", + "persistence.image.repository": "janssenproject/persistence-loader", + "persistence.image.tag": "1.0.0_dev", + "installer-settings.upgrade.image.repository": "gluufederation/upgrade", + "installer-settings.upgrade.image.tag": "4.3.0_dev" + } +} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/artifacthub-repo.yml b/helm/pygluu/kubernetes/templates/helm/artifacthub-repo.yml new file mode 100644 index 00000000000..b3d78fcc354 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/artifacthub-repo.yml @@ -0,0 +1 @@ +repositoryID: 71c4a68a-487a-44ba-8385-33571f44b28f \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/Chart.yaml new file mode 100644 index 00000000000..0e9d5e819c8 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/Chart.yaml @@ -0,0 +1,114 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +kubeVersion: ">=v1.21.0-0" +annotations: + artifacthub.io/changes: | + - Gluu 5.0 Openbanking Distribution. Auth-server and config-api. + - Updated new images + - https://gluu.org/docs/openbanking + artifacthub.io/containsSecurityUpdates: "true" + artifacthub.io/images: | + - name: auth-server + image: janssenproject/auth-server:1.0.0-beta.14 + - name: auth-server-key-rotation + image: janssenproject/certmanager:1.0.0-beta.14 + - name: client-api + image: janssenproject/client-api:1.0.0-beta.14 + - name: configuration-manager + image: janssenproject/configurator:1.0.0-beta.14 + - name: config-api + image: janssenproject/config-api:1.0.0-beta.14 + - name: fido2 + image: janssenproject/fido2:1.0.0-beta.14 + - name: opendj + image: gluufederation/opendj:5.0.0_dev + - name: persistence + image: janssenproject/persistence-loader:1.0.0-beta.14 + - name: scim + image: janssenproject/scim:1.0.0-beta.14 + artifacthub.io/license: Apache-2.0 + artifacthub.io/prerelease: "true" + catalog.cattle.io/certified: partner + catalog.cattle.io/release-name: gluu + catalog.cattle.io/display-name: Gluu Cloud Identity and Access Management +apiVersion: v2 +appVersion: "5.0.0" +icon: https://gluu.org/docs/gluu-server/favicon.ico +home: https://www.gluu.org +sources: + - https://gluu.org/docs/gluu-server + - https://github.com/GluuFederation/cloud-native-edition +maintainers: +- name: moabu + email: support@gluu.org +description: Gluu Access and Identity Management OpenBanking distribution +name: gluu +version: 5.0.2 +dependencies: + - name: config + condition: global.config.enabled + version: 5.0.2 + + - name: config-api + condition: global.config-api.enabled + version: 5.0.2 + + - name: opendj + condition: global.opendj.enabled + version: 5.0.2 + + - name: jackrabbit + condition: global.jackrabbit.enabled + version: 5.0.2 + + - name: auth-server + condition: global.auth-server.enabled + version: 5.0.2 + + - name: admin-ui + condition: global.admin-ui.enabled + version: 5.0.2 + + - name: fido2 + condition: global.fido2.enabled + version: 5.0.2 + + - name: scim + condition: global.scim.enabled + version: 5.0.2 + + - name: nginx-ingress + condition: global.nginx-ingress.enabled + version: 5.0.2 + + - name: oxshibboleth + condition: global.oxshibboleth.enabled + version: 5.0.2 + + - name: oxpassport + version: 5.0.2 + condition: config.configmap.cnPassportEnabled + + - name: casa + version: 5.0.2 + condition: config.configmap.cnCasaEnabled + + - name: auth-server-key-rotation + condition: global.auth-server-key-rotation.enabled + version: 5.0.2 + + - name: cr-rotate + version: 5.0.2 + condition: global.cr-rotate.enabled + + - name: client-api + condition: global.client-api.enabled + version: 5.0.2 + + - name: persistence + condition: global.persistence.enabled + version: 5.0.2 + + - name: cn-istio-ingress + condition: global.istio.ingress + version: 5.0.2 diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/README.md new file mode 100644 index 00000000000..ef174387224 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/README.md @@ -0,0 +1,651 @@ +# gluu + +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) + +Gluu Access and Identity Management OpenBanking distribution + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| moabu | support@gluu.org | | + +## Source Code + +* +* + +## Requirements + +Kubernetes: `>=v1.21.0-0` + +| Repository | Name | Version | +|------------|------|---------| +| | admin-ui | 5.0.2 | +| | auth-server | 5.0.2 | +| | auth-server-key-rotation | 5.0.2 | +| | casa | 5.0.2 | +| | client-api | 5.0.2 | +| | cn-istio-ingress | 5.0.2 | +| | config | 5.0.2 | +| | config-api | 5.0.2 | +| | cr-rotate | 5.0.2 | +| | fido2 | 5.0.2 | +| | jackrabbit | 5.0.2 | +| | nginx-ingress | 5.0.2 | +| | opendj | 5.0.2 | +| | oxpassport | 5.0.2 | +| | oxshibboleth | 5.0.2 | +| | persistence | 5.0.2 | +| | scim | 5.0.2 | + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| admin-ui | object | `{"additionalAnnotations":{},"additionalLabels":{},"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"gluufederation/admin-ui","tag":"1.0.0-beta.14"},"livenessProbe":{"failureThreshold":20,"initialDelaySeconds":60,"periodSeconds":25,"tcpSocket":{"port":1636},"timeoutSeconds":5},"readinessProbe":{"failureThreshold":20,"initialDelaySeconds":60,"periodSeconds":25,"tcpSocket":{"port":1636},"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"2500m","memory":"2500Mi"},"requests":{"cpu":"2500m","memory":"2500Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Admin GUI for configuration of the auth-server | +| admin-ui.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| admin-ui.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| admin-ui.dnsConfig | object | `{}` | Add custom dns config | +| admin-ui.dnsPolicy | string | `""` | Add custom dns policy | +| admin-ui.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | +| admin-ui.hpa.behavior | object | `{}` | Scaling Policies | +| admin-ui.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| admin-ui.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| admin-ui.image.pullSecrets | list | `[]` | Image Pull Secrets | +| admin-ui.image.repository | string | `"gluufederation/admin-ui"` | Image to use for deploying. | +| admin-ui.image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | +| admin-ui.livenessProbe | object | `{"failureThreshold":20,"initialDelaySeconds":60,"periodSeconds":25,"tcpSocket":{"port":1636},"timeoutSeconds":5}` | Configure the liveness healthcheck for the admin ui if needed. | +| admin-ui.readinessProbe | object | `{"failureThreshold":20,"initialDelaySeconds":60,"periodSeconds":25,"tcpSocket":{"port":1636},"timeoutSeconds":5}` | Configure the readiness healthcheck for the admin ui if needed. | +| admin-ui.replicas | int | `1` | Service replica number. | +| admin-ui.resources | object | `{"limits":{"cpu":"2500m","memory":"2500Mi"},"requests":{"cpu":"2500m","memory":"2500Mi"}}` | Resource specs. | +| admin-ui.resources.limits.cpu | string | `"2500m"` | CPU limit. | +| admin-ui.resources.limits.memory | string | `"2500Mi"` | Memory limit. | +| admin-ui.resources.requests.cpu | string | `"2500m"` | CPU request. | +| admin-ui.resources.requests.memory | string | `"2500Mi"` | Memory request. | +| admin-ui.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| admin-ui.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| admin-ui.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| admin-ui.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| admin-ui.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| auth-server | object | `{"additionalAnnotations":{},"additionalLabels":{},"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"janssenproject/auth-server","tag":"1.0.0-beta.14"},"livenessProbe":{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"readinessProbe":{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"2500m","memory":"2500Mi"},"requests":{"cpu":"2500m","memory":"2500Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | OAuth Authorization Server, the OpenID Connect Provider, the UMA Authorization Server--this is the main Internet facing component of Gluu. It's the service that returns tokens, JWT's and identity assertions. This service must be Internet facing. | +| auth-server-key-rotation | object | `{"additionalAnnotations":{},"additionalLabels":{},"dnsConfig":{},"dnsPolicy":"","image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"janssenproject/certmanager","tag":"1.0.0-beta.14"},"keysLife":48,"resources":{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Responsible for regenerating auth-keys per x hours | +| auth-server-key-rotation.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| auth-server-key-rotation.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| auth-server-key-rotation.dnsConfig | object | `{}` | Add custom dns config | +| auth-server-key-rotation.dnsPolicy | string | `""` | Add custom dns policy | +| auth-server-key-rotation.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| auth-server-key-rotation.image.pullSecrets | list | `[]` | Image Pull Secrets | +| auth-server-key-rotation.image.repository | string | `"janssenproject/certmanager"` | Image to use for deploying. | +| auth-server-key-rotation.image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | +| auth-server-key-rotation.keysLife | int | `48` | Auth server key rotation keys life in hours | +| auth-server-key-rotation.resources | object | `{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}}` | Resource specs. | +| auth-server-key-rotation.resources.limits.cpu | string | `"300m"` | CPU limit. | +| auth-server-key-rotation.resources.limits.memory | string | `"300Mi"` | Memory limit. | +| auth-server-key-rotation.resources.requests.cpu | string | `"300m"` | CPU request. | +| auth-server-key-rotation.resources.requests.memory | string | `"300Mi"` | Memory request. | +| auth-server-key-rotation.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| auth-server-key-rotation.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| auth-server-key-rotation.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| auth-server-key-rotation.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| auth-server-key-rotation.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| auth-server.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| auth-server.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| auth-server.dnsConfig | object | `{}` | Add custom dns config | +| auth-server.dnsPolicy | string | `""` | Add custom dns policy | +| auth-server.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | +| auth-server.hpa.behavior | object | `{}` | Scaling Policies | +| auth-server.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| auth-server.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| auth-server.image.pullSecrets | list | `[]` | Image Pull Secrets | +| auth-server.image.repository | string | `"janssenproject/auth-server"` | Image to use for deploying. | +| auth-server.image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | +| auth-server.livenessProbe | object | `{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for the auth server if needed. | +| auth-server.livenessProbe.exec | object | `{"command":["python3","/app/scripts/healthcheck.py"]}` | Executes the python3 healthcheck. https://github.com/JanssenProject/docker-jans-auth-server/blob/master/scripts/healthcheck.py | +| auth-server.readinessProbe | object | `{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the auth server if needed. https://github.com/JanssenProject/docker-jans-auth-server/blob/master/scripts/healthcheck.py | +| auth-server.replicas | int | `1` | Service replica number. | +| auth-server.resources | object | `{"limits":{"cpu":"2500m","memory":"2500Mi"},"requests":{"cpu":"2500m","memory":"2500Mi"}}` | Resource specs. | +| auth-server.resources.limits.cpu | string | `"2500m"` | CPU limit. | +| auth-server.resources.limits.memory | string | `"2500Mi"` | Memory limit. | +| auth-server.resources.requests.cpu | string | `"2500m"` | CPU request. | +| auth-server.resources.requests.memory | string | `"2500Mi"` | Memory request. | +| auth-server.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| auth-server.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| auth-server.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| auth-server.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| auth-server.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| casa | object | `{"additionalAnnotations":{},"additionalLabels":{},"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"gluufederation/casa","tag":"5.0.0_dev"},"livenessProbe":{"httpGet":{"path":"/casa/health-check","port":"http-casa"},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"readinessProbe":{"httpGet":{"path":"/casa/health-check","port":"http-casa"},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"500m","memory":"500Mi"},"requests":{"cpu":"500m","memory":"500Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Gluu Casa ("Casa") is a self-service web portal for end-users to manage authentication and authorization preferences for their account in a Gluu Server. | +| casa.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| casa.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| casa.dnsConfig | object | `{}` | Add custom dns config | +| casa.dnsPolicy | string | `""` | Add custom dns policy | +| casa.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | +| casa.hpa.behavior | object | `{}` | Scaling Policies | +| casa.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| casa.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| casa.image.pullSecrets | list | `[]` | Image Pull Secrets | +| casa.image.repository | string | `"gluufederation/casa"` | Image to use for deploying. | +| casa.image.tag | string | `"5.0.0_dev"` | Image tag to use for deploying. | +| casa.livenessProbe | object | `{"httpGet":{"path":"/casa/health-check","port":"http-casa"},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the liveness healthcheck for casa if needed. | +| casa.livenessProbe.httpGet.path | string | `"/casa/health-check"` | http liveness probe endpoint | +| casa.readinessProbe | object | `{"httpGet":{"path":"/casa/health-check","port":"http-casa"},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the readiness healthcheck for the casa if needed. | +| casa.readinessProbe.httpGet.path | string | `"/casa/health-check"` | http readiness probe endpoint | +| casa.replicas | int | `1` | Service replica number. | +| casa.resources | object | `{"limits":{"cpu":"500m","memory":"500Mi"},"requests":{"cpu":"500m","memory":"500Mi"}}` | Resource specs. | +| casa.resources.limits.cpu | string | `"500m"` | CPU limit. | +| casa.resources.limits.memory | string | `"500Mi"` | Memory limit. | +| casa.resources.requests.cpu | string | `"500m"` | CPU request. | +| casa.resources.requests.memory | string | `"500Mi"` | Memory request. | +| casa.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| casa.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| casa.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| casa.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| casa.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| client-api | object | `{"additionalAnnotations":{},"additionalLabels":{},"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"janssenproject/client-api","tag":"1.0.0-beta.14"},"livenessProbe":{"exec":{"command":["curl","-k","https://localhost:8443/health-check"]},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"readinessProbe":{"initialDelaySeconds":60,"periodSeconds":25,"tcpSocket":{"port":8443},"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"1000m","memory":"400Mi"},"requests":{"cpu":"1000m","memory":"400Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Middleware API to help application developers call an OAuth, OpenID or UMA server. You may wonder why this is necessary. It makes it easier for client developers to use OpenID signing and encryption features, without becoming crypto experts. This API provides some high level endpoints to do some of the heavy lifting. | +| client-api.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| client-api.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| client-api.dnsConfig | object | `{}` | Add custom dns config | +| client-api.dnsPolicy | string | `""` | Add custom dns policy | +| client-api.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | +| client-api.hpa.behavior | object | `{}` | Scaling Policies | +| client-api.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| client-api.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| client-api.image.pullSecrets | list | `[]` | Image Pull Secrets | +| client-api.image.repository | string | `"janssenproject/client-api"` | Image to use for deploying. | +| client-api.image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | +| client-api.livenessProbe | object | `{"exec":{"command":["curl","-k","https://localhost:8443/health-check"]},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for the auth server if needed. | +| client-api.livenessProbe.exec | object | `{"command":["curl","-k","https://localhost:8443/health-check"]}` | Executes the python3 healthcheck. | +| client-api.readinessProbe | object | `{"initialDelaySeconds":60,"periodSeconds":25,"tcpSocket":{"port":8443},"timeoutSeconds":5}` | Configure the readiness healthcheck for the auth server if needed. | +| client-api.replicas | int | `1` | Service replica number. | +| client-api.resources | object | `{"limits":{"cpu":"1000m","memory":"400Mi"},"requests":{"cpu":"1000m","memory":"400Mi"}}` | Resource specs. | +| client-api.resources.limits.cpu | string | `"1000m"` | CPU limit. | +| client-api.resources.limits.memory | string | `"400Mi"` | Memory limit. | +| client-api.resources.requests.cpu | string | `"1000m"` | CPU request. | +| client-api.resources.requests.memory | string | `"400Mi"` | Memory request. | +| client-api.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| client-api.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| client-api.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| client-api.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| client-api.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| config | object | `{"additionalAnnotations":{},"additionalLabels":{},"adminPassword":"Test1234#","city":"Austin","configmap":{"cnCacheType":"NATIVE_PERSISTENCE","cnCasaEnabled":false,"cnClientApiAdminCertCn":"client-api","cnClientApiApplicationCertCn":"client-api","cnClientApiBindIpAddresses":"*","cnConfigGoogleSecretNamePrefix":"gluu","cnConfigGoogleSecretVersionId":"latest","cnConfigKubernetesConfigMap":"cn","cnCouchbaseBucketPrefix":"jans","cnCouchbaseCertFile":"/etc/certs/couchbase.crt","cnCouchbaseCrt":"SWFtTm90YVNlcnZpY2VBY2NvdW50Q2hhbmdlTWV0b09uZQo=","cnCouchbaseIndexNumReplica":0,"cnCouchbasePassword":"P@ssw0rd","cnCouchbasePasswordFile":"/etc/gluu/conf/couchbase_password","cnCouchbaseSuperUser":"admin","cnCouchbaseSuperUserPassword":"Test1234#","cnCouchbaseSuperUserPasswordFile":"/etc/gluu/conf/couchbase_superuser_password","cnCouchbaseUrl":"cbgluu.default.svc.cluster.local","cnCouchbaseUser":"gluu","cnDocumentStoreType":"JCA","cnGoogleProjectId":"google-project-to-save-config-and-secrets-to","cnGoogleSecretManagerPassPhrase":"Test1234#","cnGoogleSecretManagerServiceAccount":"SWFtTm90YVNlcnZpY2VBY2NvdW50Q2hhbmdlTWV0b09uZQo=","cnGoogleSpannerDatabaseId":"","cnGoogleSpannerInstanceId":"","cnJackrabbitAdminId":"admin","cnJackrabbitAdminIdFile":"/etc/gluu/conf/jackrabbit_admin_id","cnJackrabbitAdminPasswordFile":"/etc/gluu/conf/jackrabbit_admin_password","cnJackrabbitPostgresDatabaseName":"jackrabbit","cnJackrabbitPostgresHost":"postgresql.postgres.svc.cluster.local","cnJackrabbitPostgresPasswordFile":"/etc/gluu/conf/postgres_password","cnJackrabbitPostgresPort":5432,"cnJackrabbitPostgresUser":"jackrabbit","cnJackrabbitSyncInterval":300,"cnJackrabbitUrl":"http://jackrabbit:8080","cnJettyRequestHeaderSize":8192,"cnLdapUrl":"opendj:1636","cnMaxRamPercent":"75.0","cnPassportEnabled":false,"cnPersistenceLdapMapping":"default","cnRedisSentinelGroup":"","cnRedisSslTruststore":"","cnRedisType":"STANDALONE","cnRedisUrl":"redis.redis.svc.cluster.local:6379","cnRedisUseSsl":false,"cnSamlEnabled":false,"cnScimProtectionMode":"OAUTH","cnSecretGoogleSecretNamePrefix":"gluu","cnSecretGoogleSecretVersionId":"latest","cnSecretKubernetesSecret":"cn","cnSqlDbDialect":"mysql","cnSqlDbHost":"my-release-mysql.default.svc.cluster.local","cnSqlDbName":"jans","cnSqlDbPort":3306,"cnSqlDbTimezone":"UTC","cnSqlDbUser":"jans","cnSqlPasswordFile":"/etc/jans/conf/sql_password","cnSqldbUserPassword":"Test1234#","lbAddr":""},"countryCode":"US","dnsConfig":{},"dnsPolicy":"","email":"support@gluu.org","image":{"pullSecrets":[],"repository":"janssenproject/configurator","tag":"1.0.0-beta.14"},"ldapPassword":"P@ssw0rds","migration":{"enabled":false,"migrationDataFormat":"ldif","migrationDir":"/ce-migration"},"orgName":"Gluu","redisPassword":"P@assw0rd","resources":{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}},"state":"TX","usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Configuration parameters for setup and initial configuration secret and config layers used by Gluu services. | +| config-api | object | `{"additionalAnnotations":{},"additionalLabels":{},"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"janssenproject/config-api","tag":"1.0.0-beta.14"},"livenessProbe":{"httpGet":{"path":"/jans-config-api/api/v1/health/live","port":8074},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"readinessProbe":{"httpGet":{"path":"jans-config-api/api/v1/health/ready","port":8074},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"1000m","memory":"400Mi"},"requests":{"cpu":"1000m","memory":"400Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Config Api endpoints can be used to configure the auth-server, which is an open-source OpenID Connect Provider (OP) and UMA Authorization Server (AS). | +| config-api.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| config-api.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| config-api.dnsConfig | object | `{}` | Add custom dns config | +| config-api.dnsPolicy | string | `""` | Add custom dns policy | +| config-api.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | +| config-api.hpa.behavior | object | `{}` | Scaling Policies | +| config-api.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| config-api.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| config-api.image.pullSecrets | list | `[]` | Image Pull Secrets | +| config-api.image.repository | string | `"janssenproject/config-api"` | Image to use for deploying. | +| config-api.image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | +| config-api.livenessProbe | object | `{"httpGet":{"path":"/jans-config-api/api/v1/health/live","port":8074},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for the auth server if needed. | +| config-api.livenessProbe.httpGet | object | `{"path":"/jans-config-api/api/v1/health/live","port":8074}` | http liveness probe endpoint | +| config-api.readinessProbe.httpGet | object | `{"path":"jans-config-api/api/v1/health/ready","port":8074}` | http readiness probe endpoint | +| config-api.replicas | int | `1` | Service replica number. | +| config-api.resources | object | `{"limits":{"cpu":"1000m","memory":"400Mi"},"requests":{"cpu":"1000m","memory":"400Mi"}}` | Resource specs. | +| config-api.resources.limits.cpu | string | `"1000m"` | CPU limit. | +| config-api.resources.limits.memory | string | `"400Mi"` | Memory limit. | +| config-api.resources.requests.cpu | string | `"1000m"` | CPU request. | +| config-api.resources.requests.memory | string | `"400Mi"` | Memory request. | +| config-api.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| config-api.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| config-api.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| config-api.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| config-api.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| config.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| config.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| config.adminPassword | string | `"Test1234#"` | Admin password to log in to the UI. | +| config.city | string | `"Austin"` | City. Used for certificate creation. | +| config.configmap.cnCacheType | string | `"NATIVE_PERSISTENCE"` | Cache type. `NATIVE_PERSISTENCE`, `REDIS`. or `IN_MEMORY`. Defaults to `NATIVE_PERSISTENCE` . | +| config.configmap.cnCasaEnabled | bool | `false` | Enable Casa flag . | +| config.configmap.cnClientApiAdminCertCn | string | `"client-api"` | Client-api OAuth client admin certificate common name. This should be left to the default value client-api . | +| config.configmap.cnClientApiApplicationCertCn | string | `"client-api"` | Client-api OAuth client application certificate common name. This should be left to the default value client-api. | +| config.configmap.cnClientApiBindIpAddresses | string | `"*"` | Client-api bind address. This limits what ip ranges can access the client-api. This should be left as * and controlled by a NetworkPolicy | +| config.configmap.cnConfigGoogleSecretNamePrefix | string | `"gluu"` | Prefix for Gluu configuration secret in Google Secret Manager. Defaults to gluu. If left intact gluu-configuration secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google. | +| config.configmap.cnConfigGoogleSecretVersionId | string | `"latest"` | Secret version to be used for configuration. Defaults to latest and should normally always stay that way. Used only when global.configAdapterName and global.configSecretAdapter is set to google. Used only when global.configAdapterName and global.configSecretAdapter is set to google. | +| config.configmap.cnConfigKubernetesConfigMap | string | `"cn"` | The name of the Kubernetes ConfigMap that will hold the configuration layer | +| config.configmap.cnCouchbaseBucketPrefix | string | `"jans"` | The prefix of couchbase buckets. This helps with separation in between different environments and allows for the same couchbase cluster to be used by different setups of Gluu. | +| config.configmap.cnCouchbaseCertFile | string | `"/etc/certs/couchbase.crt"` | Location of `couchbase.crt` used by Couchbase SDK for tls termination. The file path must end with couchbase.crt. In mTLS setups this is not required. | +| config.configmap.cnCouchbaseCrt | string | `"SWFtTm90YVNlcnZpY2VBY2NvdW50Q2hhbmdlTWV0b09uZQo="` | Couchbase certificate authority string. This must be encoded using base64. This can also be found in your couchbase UI Security > Root Certificate. In mTLS setups this is not required. | +| config.configmap.cnCouchbaseIndexNumReplica | int | `0` | The number of replicas per index created. Please note that the number of index nodes must be one greater than the number of index replicas. That means if your couchbase cluster only has 2 index nodes you cannot place the number of replicas to be higher than 1. | +| config.configmap.cnCouchbasePassword | string | `"P@ssw0rd"` | Couchbase password for the restricted user config.configmap.cnCouchbaseUser that is often used inside the services. The password must contain one digit, one uppercase letter, one lower case letter and one symbol . | +| config.configmap.cnCouchbasePasswordFile | string | `"/etc/gluu/conf/couchbase_password"` | The location of the Couchbase restricted user config.configmap.cnCouchbaseUser password. The file path must end with couchbase_password | +| config.configmap.cnCouchbaseSuperUser | string | `"admin"` | The Couchbase super user (admin) user name. This user is used during initialization only. | +| config.configmap.cnCouchbaseSuperUserPassword | string | `"Test1234#"` | Couchbase password for the super user config.configmap.cnCouchbaseSuperUser that is used during the initialization process. The password must contain one digit, one uppercase letter, one lower case letter and one symbol | +| config.configmap.cnCouchbaseSuperUserPasswordFile | string | `"/etc/gluu/conf/couchbase_superuser_password"` | The location of the Couchbase restricted user config.configmap.cnCouchbaseSuperUser password. The file path must end with couchbase_superuser_password. | +| config.configmap.cnCouchbaseUrl | string | `"cbgluu.default.svc.cluster.local"` | Couchbase URL. Used only when global.cnPersistenceType is hybrid or couchbase. This should be in FQDN format for either remote or local Couchbase clusters. The address can be an internal address inside the kubernetes cluster | +| config.configmap.cnCouchbaseUser | string | `"gluu"` | Couchbase restricted user. Used only when global.cnPersistenceType is hybrid or couchbase. | +| config.configmap.cnDocumentStoreType | string | `"JCA"` | Document store type to use for shibboleth files JCA or LOCAL. Note that if JCA is selected Apache Jackrabbit will be used. Jackrabbit also enables loading custom files across all services easily. | +| config.configmap.cnGoogleProjectId | string | `"google-project-to-save-config-and-secrets-to"` | Project id of the google project the secret manager belongs to. Used only when global.configAdapterName and global.configSecretAdapter is set to google. | +| config.configmap.cnGoogleSecretManagerPassPhrase | string | `"Test1234#"` | Passphrase for Gluu secret in Google Secret Manager. This is used for encrypting and decrypting data from the Google Secret Manager. Used only when global.configAdapterName and global.configSecretAdapter is set to google. | +| config.configmap.cnGoogleSpannerDatabaseId | string | `""` | Google Spanner Database ID. Used only when global.cnPersistenceType is spanner. | +| config.configmap.cnJackrabbitAdminId | string | `"admin"` | Jackrabbit admin uid. | +| config.configmap.cnJackrabbitAdminIdFile | string | `"/etc/gluu/conf/jackrabbit_admin_id"` | The location of the Jackrabbit admin uid config.cnJackrabbitAdminId. The file path must end with jackrabbit_admin_id. | +| config.configmap.cnJackrabbitAdminPasswordFile | string | `"/etc/gluu/conf/jackrabbit_admin_password"` | The location of the Jackrabbit admin password jackrabbit.secrets.cnJackrabbitAdminPassword. The file path must end with jackrabbit_admin_password. | +| config.configmap.cnJackrabbitPostgresDatabaseName | string | `"jackrabbit"` | Jackrabbit postgres database name. | +| config.configmap.cnJackrabbitPostgresHost | string | `"postgresql.postgres.svc.cluster.local"` | Postgres url | +| config.configmap.cnJackrabbitPostgresPasswordFile | string | `"/etc/gluu/conf/postgres_password"` | The location of the Jackrabbit postgres password file jackrabbit.secrets.cnJackrabbitPostgresPassword. The file path must end with postgres_password. | +| config.configmap.cnJackrabbitPostgresPort | int | `5432` | Jackrabbit Postgres port | +| config.configmap.cnJackrabbitPostgresUser | string | `"jackrabbit"` | Jackrabbit Postgres uid | +| config.configmap.cnJackrabbitSyncInterval | int | `300` | Interval between files sync (default to 300 seconds). | +| config.configmap.cnJackrabbitUrl | string | `"http://jackrabbit:8080"` | Jackrabbit internal url. Normally left as default. | +| config.configmap.cnJettyRequestHeaderSize | int | `8192` | Jetty header size in bytes in the auth server | +| config.configmap.cnMaxRamPercent | string | `"75.0"` | Value passed to Java option -XX:MaxRAMPercentage | +| config.configmap.cnPassportEnabled | bool | `false` | Boolean flag to enable/disable passport chart | +| config.configmap.cnPersistenceLdapMapping | string | `"default"` | Specify data that should be saved in LDAP (one of default, user, cache, site, token, or session; default to default). Note this environment only takes effect when `global.cnPersistenceType` is set to `hybrid`. | +| config.configmap.cnRedisSentinelGroup | string | `""` | Redis Sentinel Group. Often set when `config.configmap.cnRedisType` is set to `SENTINEL`. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | +| config.configmap.cnRedisSslTruststore | string | `""` | Redis SSL truststore. Optional. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | +| config.configmap.cnRedisType | string | `"STANDALONE"` | Redis service type. `STANDALONE` or `CLUSTER`. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | +| config.configmap.cnRedisUrl | string | `"redis.redis.svc.cluster.local:6379"` | Redis URL and port number :. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | +| config.configmap.cnRedisUseSsl | bool | `false` | Boolean to use SSL in Redis. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | +| config.configmap.cnSamlEnabled | bool | `false` | Enable SAML-related features; UI menu, etc. | +| config.configmap.cnScimProtectionMode | string | `"OAUTH"` | SCIM protection mode OAUTH|TEST|UMA | +| config.configmap.cnSecretGoogleSecretNamePrefix | string | `"gluu"` | Prefix for Gluu secret in Google Secret Manager. Defaults to gluu. If left gluu-secret secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google. | +| config.configmap.cnSecretKubernetesSecret | string | `"cn"` | Kubernetes secret name holding configuration keys. Used when global.configSecretAdapter is set to kubernetes which is the default. | +| config.configmap.cnSqlDbDialect | string | `"mysql"` | SQL database dialect. `mysql` or `pgsql` | +| config.configmap.cnSqlDbHost | string | `"my-release-mysql.default.svc.cluster.local"` | SQL database host uri. | +| config.configmap.cnSqlDbName | string | `"jans"` | SQL database name. | +| config.configmap.cnSqlDbPort | int | `3306` | SQL database port. | +| config.configmap.cnSqlDbTimezone | string | `"UTC"` | SQL database timezone. | +| config.configmap.cnSqlDbUser | string | `"jans"` | SQL database username. | +| config.configmap.cnSqlPasswordFile | string | `"/etc/jans/conf/sql_password"` | SQL password file holding password from config.configmap.cnSqldbUserPassword . | +| config.configmap.cnSqldbUserPassword | string | `"Test1234#"` | SQL password injected as config.configmap.cnSqlPasswordFile . | +| config.configmap.lbAddr | string | `""` | Loadbalancer address for AWS if the FQDN is not registered. | +| config.countryCode | string | `"US"` | Country code. Used for certificate creation. | +| config.dnsConfig | object | `{}` | Add custom dns config | +| config.dnsPolicy | string | `""` | Add custom dns policy | +| config.email | string | `"support@gluu.org"` | Email address of the administrator usually. Used for certificate creation. | +| config.image.pullSecrets | list | `[]` | Image Pull Secrets | +| config.image.repository | string | `"janssenproject/configurator"` | Image to use for deploying. | +| config.image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | +| config.ldapPassword | string | `"P@ssw0rds"` | LDAP admin password if OpennDJ is used for persistence. | +| config.migration | object | `{"enabled":false,"migrationDataFormat":"ldif","migrationDir":"/ce-migration"}` | CE to CN Migration section | +| config.migration.enabled | bool | `false` | Boolean flag to enable migration from CE | +| config.migration.migrationDataFormat | string | `"ldif"` | migration data-format depending on persistence backend. Supported data formats are ldif, couchbase+json, spanner+avro, postgresql+json, and mysql+json. | +| config.migration.migrationDir | string | `"/ce-migration"` | Directory holding all migration files | +| config.orgName | string | `"Gluu"` | Organization name. Used for certificate creation. | +| config.redisPassword | string | `"P@assw0rd"` | Redis admin password if `config.configmap.cnCacheType` is set to `REDIS`. | +| config.resources | object | `{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}}` | Resource specs. | +| config.resources.limits.cpu | string | `"300m"` | CPU limit. | +| config.resources.limits.memory | string | `"300Mi"` | Memory limit. | +| config.resources.requests.cpu | string | `"300m"` | CPU request. | +| config.resources.requests.memory | string | `"300Mi"` | Memory request. | +| config.state | string | `"TX"` | State code. Used for certificate creation. | +| config.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service. | +| config.usrEnvs.normal | object | `{}` | Add custom normal envs to the service. variable1: value1 | +| config.usrEnvs.secret | object | `{}` | Add custom secret envs to the service. variable1: value1 | +| config.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| config.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| cr-rotate | object | `{"additionalAnnotations":{},"additionalLabels":{},"dnsConfig":{},"dnsPolicy":"","image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"gluufederation/cr-rotate","tag":"5.0.0_dev"},"resources":{"limits":{"cpu":"200m","memory":"200Mi"},"requests":{"cpu":"200m","memory":"200Mi"}},"service":{"crRotateServiceName":"cr-rotate"},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | CacheRefreshRotation is a special container to monitor cache refresh on oxTrust containers. This may be depreciated. | +| cr-rotate.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| cr-rotate.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| cr-rotate.dnsConfig | object | `{}` | Add custom dns config | +| cr-rotate.dnsPolicy | string | `""` | Add custom dns policy | +| cr-rotate.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| cr-rotate.image.pullSecrets | list | `[]` | Image Pull Secrets | +| cr-rotate.image.repository | string | `"gluufederation/cr-rotate"` | Image to use for deploying. | +| cr-rotate.image.tag | string | `"5.0.0_dev"` | Image tag to use for deploying. | +| cr-rotate.resources | object | `{"limits":{"cpu":"200m","memory":"200Mi"},"requests":{"cpu":"200m","memory":"200Mi"}}` | Resource specs. | +| cr-rotate.resources.limits.cpu | string | `"200m"` | CPU limit. | +| cr-rotate.resources.limits.memory | string | `"200Mi"` | Memory limit. | +| cr-rotate.resources.requests.cpu | string | `"200m"` | CPU request. | +| cr-rotate.resources.requests.memory | string | `"200Mi"` | Memory request. | +| cr-rotate.service.crRotateServiceName | string | `"cr-rotate"` | Name of the cr-rotate service. Please keep it as default. | +| cr-rotate.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| cr-rotate.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| cr-rotate.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| cr-rotate.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| cr-rotate.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| fido2 | object | `{"additionalAnnotations":{},"additionalLabels":{},"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"janssenproject/fido2","tag":"1.0.0-beta.14"},"livenessProbe":{"httpGet":{"path":"/jans-fido2/sys/health-check","port":"http-fido2"},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"readinessProbe":{"httpGet":{"path":"/jans-fido2/sys/health-check","port":"http-fido2"},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"500m","memory":"500Mi"},"requests":{"cpu":"500m","memory":"500Mi"}},"service":{"name":"http-fido2","port":8080},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | FIDO 2.0 (FIDO2) is an open authentication standard that enables leveraging common devices to authenticate to online services in both mobile and desktop environments. | +| fido2.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| fido2.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| fido2.dnsConfig | object | `{}` | Add custom dns config | +| fido2.dnsPolicy | string | `""` | Add custom dns policy | +| fido2.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | +| fido2.hpa.behavior | object | `{}` | Scaling Policies | +| fido2.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| fido2.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| fido2.image.pullSecrets | list | `[]` | Image Pull Secrets | +| fido2.image.repository | string | `"janssenproject/fido2"` | Image to use for deploying. | +| fido2.image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | +| fido2.livenessProbe | object | `{"httpGet":{"path":"/jans-fido2/sys/health-check","port":"http-fido2"},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the liveness healthcheck for the fido2 if needed. | +| fido2.livenessProbe.httpGet | object | `{"path":"/jans-fido2/sys/health-check","port":"http-fido2"}` | http liveness probe endpoint | +| fido2.readinessProbe | object | `{"httpGet":{"path":"/jans-fido2/sys/health-check","port":"http-fido2"},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the readiness healthcheck for the fido2 if needed. | +| fido2.replicas | int | `1` | Service replica number. | +| fido2.resources | object | `{"limits":{"cpu":"500m","memory":"500Mi"},"requests":{"cpu":"500m","memory":"500Mi"}}` | Resource specs. | +| fido2.resources.limits.cpu | string | `"500m"` | CPU limit. | +| fido2.resources.limits.memory | string | `"500Mi"` | Memory limit. | +| fido2.resources.requests.cpu | string | `"500m"` | CPU request. | +| fido2.resources.requests.memory | string | `"500Mi"` | Memory request. | +| fido2.service.name | string | `"http-fido2"` | The name of the fido2 port within the fido2 service. Please keep it as default. | +| fido2.service.port | int | `8080` | Port of the fido2 service. Please keep it as default. | +| fido2.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| fido2.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| fido2.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| fido2.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| fido2.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| global | object | `{"admin-ui":{"adminUiApiKey":"xxxxxxxxxxx","adminUiApiKeyFile":"/etc/jans/conf/admin_ui_api_key","adminUiManagementKey":"xxxxxxxxxxx","adminUiManagementKeyFile":"/etc/jans/conf/admin_ui_management_key","adminUiProductCode":"xxxxxxxxxxx","adminUiProductCodeFile":"/etc/jans/conf/admin_ui_product_code","adminUiServiceName":"admin-ui","adminUiSharedKey":"xxxxxxxxxxx","adminUiSharedKeyFile":"/etc/jans/conf/admin_ui_shared_key","enabled":false},"alb":{"ingress":false},"auth-server":{"appLoggers":{"auditStatsLogLevel":"INFO","auditStatsLogTarget":"FILE","authLogLevel":"INFO","authLogTarget":"STDOUT","httpLogLevel":"INFO","httpLogTarget":"FILE","ldapStatsLogLevel":"INFO","ldapStatsLogTarget":"FILE","persistenceDurationLogLevel":"INFO","persistenceDurationLogTarget":"FILE","persistenceLogLevel":"INFO","persistenceLogTarget":"FILE","scriptLogLevel":"INFO","scriptLogTarget":"FILE"},"authEncKeys":"RSA1_5 RSA-OAEP","authServerServiceName":"auth-server","authSigKeys":"RS256 RS384 RS512 ES256 ES384 ES512 PS256 PS384 PS512","enabled":true},"auth-server-key-rotation":{"enabled":false},"awsStorageType":"io1","azureStorageAccountType":"Standard_LRS","azureStorageKind":"Managed","casa":{"casaServiceName":"casa"},"client-api":{"appLoggers":{"clientApiLogLevel":"INFO","clientApiLogTarget":"STDOUT"},"clientApiServerServiceName":"client-api","enabled":false},"cloud":{"testEnviroment":false},"cnGoogleApplicationCredentials":"/etc/jans/conf/google-credentials.json","cnJackrabbitCluster":false,"cnObExtSigningAlias":"","cnObExtSigningJwksCrt":"","cnObExtSigningJwksKey":"","cnObExtSigningJwksKeyPassPhrase":"","cnObExtSigningJwksUri":"","cnObStaticSigningKeyKid":"","cnObTransportAlias":"","cnObTransportCrt":"","cnObTransportKey":"","cnObTransportKeyPassPhrase":"","cnObTransportTrustStore":"","cnPersistenceType":"sql","config":{"enabled":true},"config-api":{"appLoggers":{"configApiLogLevel":"INFO","configApiLogTarget":"STDOUT"},"configApiServerServiceName":"config-api","enabled":true},"configAdapterName":"kubernetes","configSecretAdapter":"kubernetes","cr-rotate":{"enabled":false},"distribution":"default","fido2":{"appLoggers":{"fido2LogLevel":"INFO","fido2LogTarget":"STDOUT","persistenceLogLevel":"INFO","persistenceLogTarget":"FILE"},"enabled":true,"fido2ServiceName":"fido2"},"fqdn":"demoexample.gluu.org","gcePdStorageType":"pd-standard","isFqdnRegistered":false,"istio":{"additionalAnnotations":{},"additionalLabels":{},"enabled":false,"ingress":false,"namespace":"istio-system"},"jackrabbit":{"enabled":false,"jackRabbitServiceName":"jackrabbit"},"lbIp":"22.22.22.22","nginx-ingress":{"enabled":true},"opendj":{"enabled":false,"ldapServiceName":"opendj"},"oxpassport":{"oxPassportServiceName":"oxpassport"},"oxshibboleth":{"enabled":false,"oxShibbolethServiceName":"oxshibboleth"},"persistence":{"enabled":true},"scim":{"appLoggers":{"ldapStatsLogLevel":"INFO","ldapStatsLogTarget":"FILE","persistenceDurationLogLevel":"INFO","persistenceDurationLogTarget":"FILE","persistenceLogLevel":"INFO","persistenceLogTarget":"FILE","scimLogLevel":"INFO","scimLogTarget":"STDOUT","scriptLogLevel":"INFO","scriptLogTarget":"FILE"},"enabled":true,"scimServiceName":"scim"},"storageClass":{"allowVolumeExpansion":true,"allowedTopologies":[],"mountOptions":["debug"],"parameters":{},"provisioner":"microk8s.io/hostpath","reclaimPolicy":"Retain","volumeBindingMode":"WaitForFirstConsumer"},"upgrade":{"enabled":false},"usrEnvs":{"normal":{},"secret":{}}}` | Parameters used globally across all services helm charts. | +| global.admin-ui.adminUiApiKeyFile | string | `"/etc/jans/conf/admin_ui_api_key"` | Admin UI license API key mount location. | +| global.admin-ui.adminUiManagementKey | string | `"xxxxxxxxxxx"` | Admin UI license management key. | +| global.admin-ui.adminUiManagementKeyFile | string | `"/etc/jans/conf/admin_ui_management_key"` | Admin UI license management key mount location. | +| global.admin-ui.adminUiProductCode | string | `"xxxxxxxxxxx"` | Admin UI license product code. | +| global.admin-ui.adminUiProductCodeFile | string | `"/etc/jans/conf/admin_ui_product_code"` | Admin UI license product code mount location. | +| global.admin-ui.adminUiServiceName | string | `"admin-ui"` | Name of the admin-ui service. Please keep it as default. | +| global.admin-ui.adminUiSharedKey | string | `"xxxxxxxxxxx"` | Admin UI license shared key. | +| global.admin-ui.adminUiSharedKeyFile | string | `"/etc/jans/conf/admin_ui_shared_key"` | Admin UI license shared key mount location. | +| global.admin-ui.enabled | bool | `false` | Boolean flag to enable/disable the admin-ui chart and admin ui config api plugin. | +| global.alb.ingress | bool | `false` | Activates ALB ingress | +| global.auth-server-key-rotation.enabled | bool | `false` | Boolean flag to enable/disable the auth-server-key rotation cronjob chart. | +| global.auth-server.appLoggers | object | `{"auditStatsLogLevel":"INFO","auditStatsLogTarget":"FILE","authLogLevel":"INFO","authLogTarget":"STDOUT","httpLogLevel":"INFO","httpLogTarget":"FILE","ldapStatsLogLevel":"INFO","ldapStatsLogTarget":"FILE","persistenceDurationLogLevel":"INFO","persistenceDurationLogTarget":"FILE","persistenceLogLevel":"INFO","persistenceLogTarget":"FILE","scriptLogLevel":"INFO","scriptLogTarget":"FILE"}` | App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. | +| global.auth-server.appLoggers.auditStatsLogLevel | string | `"INFO"` | jans-auth_audit.log level | +| global.auth-server.appLoggers.auditStatsLogTarget | string | `"FILE"` | jans-auth_script.log target | +| global.auth-server.appLoggers.authLogLevel | string | `"INFO"` | jans-auth.log level | +| global.auth-server.appLoggers.authLogTarget | string | `"STDOUT"` | jans-auth.log target | +| global.auth-server.appLoggers.httpLogLevel | string | `"INFO"` | http_request_response.log level | +| global.auth-server.appLoggers.httpLogTarget | string | `"FILE"` | http_request_response.log target | +| global.auth-server.appLoggers.ldapStatsLogLevel | string | `"INFO"` | jans-auth_persistence_ldap_statistics.log level | +| global.auth-server.appLoggers.ldapStatsLogTarget | string | `"FILE"` | jans-auth_persistence_ldap_statistics.log target | +| global.auth-server.appLoggers.persistenceDurationLogLevel | string | `"INFO"` | jans-auth_persistence_duration.log level | +| global.auth-server.appLoggers.persistenceDurationLogTarget | string | `"FILE"` | jans-auth_persistence_duration.log target | +| global.auth-server.appLoggers.persistenceLogLevel | string | `"INFO"` | jans-auth_persistence.log level | +| global.auth-server.appLoggers.persistenceLogTarget | string | `"FILE"` | jans-auth_persistence.log target | +| global.auth-server.appLoggers.scriptLogLevel | string | `"INFO"` | jans-auth_script.log level | +| global.auth-server.appLoggers.scriptLogTarget | string | `"FILE"` | jans-auth_script.log target | +| global.auth-server.authEncKeys | string | `"RSA1_5 RSA-OAEP"` | space-separated key algorithm for encryption (default to `RSA1_5 RSA-OAEP`) | +| global.auth-server.authServerServiceName | string | `"auth-server"` | Name of the auth-server service. Please keep it as default. | +| global.auth-server.authSigKeys | string | `"RS256 RS384 RS512 ES256 ES384 ES512 PS256 PS384 PS512"` | space-separated key algorithm for signing (default to `RS256 RS384 RS512 ES256 ES384 ES512 PS256 PS384 PS512`) | +| global.auth-server.enabled | bool | `true` | Boolean flag to enable/disable auth-server chart. You should never set this to false. | +| global.awsStorageType | string | `"io1"` | Volume storage type if using AWS volumes. | +| global.azureStorageAccountType | string | `"Standard_LRS"` | Volume storage type if using Azure disks. | +| global.azureStorageKind | string | `"Managed"` | Azure storage kind if using Azure disks | +| global.casa.casaServiceName | string | `"casa"` | Name of the casa service. Please keep it as default. | +| global.client-api.appLoggers | object | `{"clientApiLogLevel":"INFO","clientApiLogTarget":"STDOUT"}` | App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. | +| global.client-api.appLoggers.clientApiLogLevel | string | `"INFO"` | client-api.log level | +| global.client-api.appLoggers.clientApiLogTarget | string | `"STDOUT"` | client-api.log target | +| global.client-api.clientApiServerServiceName | string | `"client-api"` | Name of the client-api service. Please keep it as default. | +| global.client-api.enabled | bool | `false` | Boolean flag to enable/disable the client-api chart. | +| global.cloud.testEnviroment | bool | `false` | Boolean flag if enabled will strip resources requests and limits from all services. | +| global.cnGoogleApplicationCredentials | string | `"/etc/jans/conf/google-credentials.json"` | Base64 encoded service account. The sa must have roles/secretmanager.admin to use Google secrets and roles/spanner.databaseUser to use Spanner. | +| global.cnJackrabbitCluster | bool | `false` | Boolean flag if enabled will enable jackrabbit in cluster mode with Postgres. | +| global.cnObExtSigningAlias | string | `""` | Open banking external signing AS Alias. This is a kid value.Used in SSA Validation, kid used while encoding a JWT sent to token URL i.e XkwIzWy44xWSlcWnMiEc8iq9s2G | +| global.cnObExtSigningJwksCrt | string | `""` | Open banking external signing jwks AS certificate authority string. Used in SSA Validation. This must be encoded using base64.. Used when `.global.cnObExtSigningJwksUri` is set. | +| global.cnObExtSigningJwksKey | string | `""` | Open banking external signing jwks AS key string. Used in SSA Validation. This must be encoded using base64. Used when `.global.cnObExtSigningJwksUri` is set. | +| global.cnObExtSigningJwksKeyPassPhrase | string | `""` | Open banking external signing jwks AS key passphrase to unlock provided key. This must be encoded using base64. Used when `.global.cnObExtSigningJwksUri` is set. | +| global.cnObExtSigningJwksUri | string | `""` | Open banking external signing jwks uri. Used in SSA Validation. | +| global.cnObStaticSigningKeyKid | string | `""` | Open banking signing AS kid to force the AS to use a specific signing key. i.e Wy44xWSlcWnMiEc8iq9s2G | +| global.cnObTransportAlias | string | `""` | Open banking transport Alias used inside the JVM. | +| global.cnObTransportCrt | string | `""` | Open banking AS transport crt. Used in SSA Validation. This must be encoded using base64. | +| global.cnObTransportKey | string | `""` | Open banking AS transport key. Used in SSA Validation. This must be encoded using base64. | +| global.cnObTransportKeyPassPhrase | string | `""` | Open banking AS transport key pas`sphrase to unlock AS transport key. This must be encoded using base64. | +| global.cnObTransportTrustStore | string | `""` | Open banking AS transport truststore crt. This is normally generated from the OB issuing CA, OB Root CA and Signing CA. Used when .global.cnObExtSigningJwksUri is set. Used in SSA Validation. This must be encoded using base64. | +| global.cnPersistenceType | string | `"sql"` | Persistence backend to run Gluu with ldap|couchbase|hybrid|sql|spanner. | +| global.config-api.appLoggers | object | `{"configApiLogLevel":"INFO","configApiLogTarget":"STDOUT"}` | App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. | +| global.config-api.appLoggers.configApiLogLevel | string | `"INFO"` | configapi.log level | +| global.config-api.appLoggers.configApiLogTarget | string | `"STDOUT"` | configapi.log target | +| global.config-api.configApiServerServiceName | string | `"config-api"` | Name of the config-api service. Please keep it as default. | +| global.config-api.enabled | bool | `true` | Boolean flag to enable/disable the config-api chart. | +| global.config.enabled | bool | `true` | Boolean flag to enable/disable the configuration chart. This normally should never be false | +| global.configAdapterName | string | `"kubernetes"` | The config backend adapter that will hold Gluu configuration layer. google|kubernetes | +| global.configSecretAdapter | string | `"kubernetes"` | The config backend adapter that will hold Gluu secret layer. google|kubernetes | +| global.cr-rotate.enabled | bool | `false` | Boolean flag to enable/disable the cr-rotate chart. | +| global.distribution | string | `"default"` | Gluu distributions supported are: default|openbanking. | +| global.fido2.appLoggers | object | `{"fido2LogLevel":"INFO","fido2LogTarget":"STDOUT","persistenceLogLevel":"INFO","persistenceLogTarget":"FILE"}` | App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. | +| global.fido2.appLoggers.fido2LogLevel | string | `"INFO"` | fido2.log level | +| global.fido2.appLoggers.fido2LogTarget | string | `"STDOUT"` | fido2.log target | +| global.fido2.appLoggers.persistenceLogLevel | string | `"INFO"` | fido2_persistence.log level | +| global.fido2.appLoggers.persistenceLogTarget | string | `"FILE"` | fido2_persistence.log target | +| global.fido2.enabled | bool | `true` | Boolean flag to enable/disable the fido2 chart. | +| global.fido2.fido2ServiceName | string | `"fido2"` | Name of the fido2 service. Please keep it as default. | +| global.fqdn | string | `"demoexample.gluu.org"` | Fully qualified domain name to be used for Gluu installation. This address will be used to reach Gluu services. | +| global.gcePdStorageType | string | `"pd-standard"` | GCE storage kind if using Google disks | +| global.isFqdnRegistered | bool | `false` | Boolean flag to enable mapping global.lbIp to global.fqdn inside pods on clouds that provide static ip for loadbalancers. On cloud that provide only addresses to the LB this flag will enable a script to actively scan config.configmap.lbAddr and update the hosts file inside the pods automatically. | +| global.istio.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| global.istio.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| global.istio.enabled | bool | `false` | Boolean flag that enables using istio side cars with Gluu services. | +| global.istio.ingress | bool | `false` | Boolean flag that enables using istio gateway for Gluu. This assumes istio ingress is installed and hence the LB is available. | +| global.istio.namespace | string | `"istio-system"` | The namespace istio is deployed in. The is normally istio-system. | +| global.jackrabbit.enabled | bool | `false` | Boolean flag to enable/disable the jackrabbit chart. For more information on how it is used inside Gluu https://gluu.org/docs/gluu-server/4.2/installation-guide/install-kubernetes/#working-with-jackrabbit. If disabled oxShibboleth cannot be run. | +| global.jackrabbit.jackRabbitServiceName | string | `"jackrabbit"` | Name of the Jackrabbit service. Please keep it as default. | +| global.lbIp | string | `"22.22.22.22"` | The Loadbalancer IP created by nginx or istio on clouds that provide static IPs. This is not needed if `global.fqdn` is globally resolvable. | +| global.nginx-ingress.enabled | bool | `true` | Boolean flag to enable/disable the nginx-ingress definitions chart. | +| global.opendj.enabled | bool | `false` | Boolean flag to enable/disable the OpenDJ chart. | +| global.opendj.ldapServiceName | string | `"opendj"` | Name of the OpenDJ service. Please keep it as default. | +| global.oxpassport.oxPassportServiceName | string | `"oxpassport"` | Name of the oxPassport service. Please keep it as default. | +| global.oxshibboleth.enabled | bool | `false` | Boolean flag to enable/disable the oxShibbboleth chart. | +| global.oxshibboleth.oxShibbolethServiceName | string | `"oxshibboleth"` | Name of the oxShibboleth service. Please keep it as default. | +| global.persistence.enabled | bool | `true` | Boolean flag to enable/disable the persistence chart. | +| global.scim.appLoggers | object | `{"ldapStatsLogLevel":"INFO","ldapStatsLogTarget":"FILE","persistenceDurationLogLevel":"INFO","persistenceDurationLogTarget":"FILE","persistenceLogLevel":"INFO","persistenceLogTarget":"FILE","scimLogLevel":"INFO","scimLogTarget":"STDOUT","scriptLogLevel":"INFO","scriptLogTarget":"FILE"}` | App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. | +| global.scim.appLoggers.ldapStatsLogLevel | string | `"INFO"` | jans-scim_persistence_ldap_statistics.log level | +| global.scim.appLoggers.ldapStatsLogTarget | string | `"FILE"` | jans-scim_persistence_ldap_statistics.log target | +| global.scim.appLoggers.persistenceDurationLogLevel | string | `"INFO"` | jans-scim_persistence_duration.log level | +| global.scim.appLoggers.persistenceDurationLogTarget | string | `"FILE"` | jans-scim_persistence_duration.log target | +| global.scim.appLoggers.persistenceLogLevel | string | `"INFO"` | jans-scim_persistence.log level | +| global.scim.appLoggers.persistenceLogTarget | string | `"FILE"` | jans-scim_persistence.log target | +| global.scim.appLoggers.scimLogLevel | string | `"INFO"` | jans-scim.log level | +| global.scim.appLoggers.scimLogTarget | string | `"STDOUT"` | jans-scim.log target | +| global.scim.appLoggers.scriptLogLevel | string | `"INFO"` | jans-scim_script.log level | +| global.scim.appLoggers.scriptLogTarget | string | `"FILE"` | jans-scim_script.log target | +| global.scim.enabled | bool | `true` | Boolean flag to enable/disable the SCIM chart. | +| global.scim.scimServiceName | string | `"scim"` | Name of the scim service. Please keep it as default. | +| global.storageClass | object | `{"allowVolumeExpansion":true,"allowedTopologies":[],"mountOptions":["debug"],"parameters":{},"provisioner":"microk8s.io/hostpath","reclaimPolicy":"Retain","volumeBindingMode":"WaitForFirstConsumer"}` | StorageClass section for Jackrabbit and OpenDJ charts. This is not currently used by the openbanking distribution. You may specify custom parameters as needed. | +| global.storageClass.parameters | object | `{}` | parameters: | +| global.upgrade.enabled | bool | `false` | Boolean flag used when running upgrading through versions command. Used when upgrading with LDAP as the persistence to load the 101x ldif. | +| global.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service. Envs defined in global.userEnvs will be globally available to all services | +| global.usrEnvs.normal | object | `{}` | Add custom normal envs to the service. variable1: value1 | +| global.usrEnvs.secret | object | `{}` | Add custom secret envs to the service. variable1: value1 | +| installer-settings | object | `{"acceptLicense":"","aws":{"arn":{"arnAcmCert":"","enabled":""},"lbType":"","vpcCidr":"0.0.0.0/0"},"confirmSettings":false,"couchbase":{"backup":{"fullSchedule":"","incrementalSchedule":"","retentionTime":"","storageSize":""},"clusterName":"","commonName":"","customFileOverride":"","install":"","lowResourceInstall":"","namespace":"","subjectAlternativeName":"","totalNumberOfExpectedTransactionsPerSec":"","totalNumberOfExpectedUsers":"","volumeType":""},"currentVersion":"","google":{"useSecretManager":""},"images":{"edit":""},"jackrabbit":{"clusterMode":""},"ldap":{"backup":{"fullSchedule":""},"multiClusterIds":[],"subsequentCluster":""},"namespace":"","nginxIngress":{"namespace":"","releaseName":""},"nodes":{"ips":"","names":"","zones":""},"openbanking":{"cnObTransportTrustStoreP12password":"","hasCnObTransportTrustStore":false},"postgres":{"install":"","namespace":""},"redis":{"install":"","namespace":""},"releaseName":"","sql":{"install":"","namespace":""},"upgrade":{"image":{"repository":"","tag":""},"targetVersion":""},"volumeProvisionStrategy":""}` | Only used by the installer. These settings do not affect nor are used by the chart | +| jackrabbit | object | `{"additionalAnnotations":{},"additionalLabels":{},"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"gluufederation/jackrabbit","tag":"5.0.0_dev"},"livenessProbe":{"initialDelaySeconds":25,"periodSeconds":25,"tcpSocket":{"port":"http-jackrabbit"},"timeoutSeconds":5},"readinessProbe":{"initialDelaySeconds":30,"periodSeconds":30,"tcpSocket":{"port":"http-jackrabbit"},"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"1500m","memory":"1000Mi"},"requests":{"cpu":"1500m","memory":"1000Mi"}},"secrets":{"cnJackrabbitAdminPassword":"Test1234#","cnJackrabbitPostgresPassword":"P@ssw0rd"},"storage":{"size":"5Gi"},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Jackrabbit Oak is a complementary implementation of the JCR specification. It is an effort to implement a scalable and performant hierarchical content repository for use as the foundation of modern world-class web sites and other demanding content applications https://jackrabbit.apache.org/jcr/index.html | +| jackrabbit.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| jackrabbit.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| jackrabbit.dnsConfig | object | `{}` | Add custom dns config | +| jackrabbit.dnsPolicy | string | `""` | Add custom dns policy | +| jackrabbit.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | +| jackrabbit.hpa.behavior | object | `{}` | Scaling Policies | +| jackrabbit.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| jackrabbit.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| jackrabbit.image.pullSecrets | list | `[]` | Image Pull Secrets | +| jackrabbit.image.repository | string | `"gluufederation/jackrabbit"` | Image to use for deploying. | +| jackrabbit.image.tag | string | `"5.0.0_dev"` | Image tag to use for deploying. | +| jackrabbit.livenessProbe | object | `{"initialDelaySeconds":25,"periodSeconds":25,"tcpSocket":{"port":"http-jackrabbit"},"timeoutSeconds":5}` | Configure the liveness healthcheck for the Jackrabbit if needed. | +| jackrabbit.livenessProbe.tcpSocket | object | `{"port":"http-jackrabbit"}` | Executes tcp healthcheck. | +| jackrabbit.readinessProbe | object | `{"initialDelaySeconds":30,"periodSeconds":30,"tcpSocket":{"port":"http-jackrabbit"},"timeoutSeconds":5}` | Configure the readiness healthcheck for the Jackrabbit if needed. | +| jackrabbit.readinessProbe.tcpSocket | object | `{"port":"http-jackrabbit"}` | Executes tcp healthcheck. | +| jackrabbit.replicas | int | `1` | Service replica number. | +| jackrabbit.resources | object | `{"limits":{"cpu":"1500m","memory":"1000Mi"},"requests":{"cpu":"1500m","memory":"1000Mi"}}` | Resource specs. | +| jackrabbit.resources.limits.cpu | string | `"1500m"` | CPU limit. | +| jackrabbit.resources.limits.memory | string | `"1000Mi"` | Memory limit. | +| jackrabbit.resources.requests.cpu | string | `"1500m"` | CPU request. | +| jackrabbit.resources.requests.memory | string | `"1000Mi"` | Memory request. | +| jackrabbit.secrets.cnJackrabbitAdminPassword | string | `"Test1234#"` | Jackrabbit admin uid password | +| jackrabbit.secrets.cnJackrabbitPostgresPassword | string | `"P@ssw0rd"` | Jackrabbit Postgres uid password | +| jackrabbit.storage.size | string | `"5Gi"` | Jackrabbit volume size | +| jackrabbit.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| jackrabbit.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| jackrabbit.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| jackrabbit.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| jackrabbit.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| nginx-ingress | object | `{"ingress":{"additionalAnnotations":{},"additionalLabels":{},"adminUiAdditionalAnnotations":{},"adminUiEnabled":false,"adminUiLabels":{},"authServerAdditionalAnnotations":{},"authServerEnabled":true,"authServerLabels":{},"authServerProtectedRegister":false,"authServerProtectedRegisterAdditionalAnnotations":{},"authServerProtectedRegisterLabels":{},"authServerProtectedToken":false,"authServerProtectedTokenAdditionalAnnotations":{},"authServerProtectedTokenLabels":{},"configApiAdditionalAnnotations":{},"configApiEnabled":true,"configApiLabels":{},"fido2ConfigAdditionalAnnotations":{},"fido2ConfigEnabled":false,"fido2ConfigLabels":{},"hosts":["demoexample.gluu.org"],"openidAdditionalAnnotations":{},"openidConfigEnabled":true,"openidConfigLabels":{},"path":"/","scimAdditionalAnnotations":{},"scimConfigAdditionalAnnotations":{},"scimConfigEnabled":false,"scimConfigLabels":{},"scimEnabled":false,"scimLabels":{},"tls":[{"hosts":["demoexample.gluu.org"],"secretName":"tls-certificate"}],"u2fAdditionalAnnotations":{},"u2fConfigEnabled":true,"u2fConfigLabels":{},"uma2AdditionalAnnotations":{},"uma2ConfigEnabled":true,"uma2ConfigLabels":{},"webdiscoveryAdditionalAnnotations":{},"webdiscoveryEnabled":true,"webdiscoveryLabels":{},"webfingerAdditionalAnnotations":{},"webfingerEnabled":true,"webfingerLabels":{}}}` | Nginx ingress definitions chart | +| nginx-ingress.ingress.additionalAnnotations | object | `{}` | Additional annotations that will be added across all ingress definitions in the format of {cert-manager.io/issuer: "letsencrypt-prod"} Enable client certificate authentication nginx.ingress.kubernetes.io/auth-tls-verify-client: "optional" Create the secret containing the trusted ca certificates nginx.ingress.kubernetes.io/auth-tls-secret: "gluu/tls-certificate" Specify the verification depth in the client certificates chain nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1" Specify if certificates are passed to upstream server nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true" | +| nginx-ingress.ingress.additionalLabels | object | `{}` | Additional labels that will be added across all ingress definitions in the format of {mylabel: "myapp"} | +| nginx-ingress.ingress.adminUiAdditionalAnnotations | object | `{}` | openid-configuration ingress resource additional annotations. | +| nginx-ingress.ingress.adminUiEnabled | bool | `false` | Enable Admin UI endpoints. COMING SOON. | +| nginx-ingress.ingress.adminUiLabels | object | `{}` | Admin UI ingress resource labels. key app is taken. | +| nginx-ingress.ingress.authServerAdditionalAnnotations | object | `{}` | Auth server ingress resource additional annotations. | +| nginx-ingress.ingress.authServerEnabled | bool | `true` | Enable Auth server endpoints /jans-auth | +| nginx-ingress.ingress.authServerLabels | object | `{}` | Auth server ingress resource labels. key app is taken | +| nginx-ingress.ingress.authServerProtectedRegister | bool | `false` | Enable mTLS onn Auth server endpoint /jans-auth/restv1/register | +| nginx-ingress.ingress.authServerProtectedRegisterAdditionalAnnotations | object | `{}` | Auth server protected register ingress resource additional annotations. | +| nginx-ingress.ingress.authServerProtectedRegisterLabels | object | `{}` | Auth server protected token ingress resource labels. key app is taken | +| nginx-ingress.ingress.authServerProtectedToken | bool | `false` | Enable mTLS on Auth server endpoint /jans-auth/restv1/token | +| nginx-ingress.ingress.authServerProtectedTokenAdditionalAnnotations | object | `{}` | Auth server protected token ingress resource additional annotations. | +| nginx-ingress.ingress.authServerProtectedTokenLabels | object | `{}` | Auth server protected token ingress resource labels. key app is taken | +| nginx-ingress.ingress.configApiAdditionalAnnotations | object | `{}` | ConfigAPI ingress resource additional annotations. | +| nginx-ingress.ingress.configApiLabels | object | `{}` | configAPI ingress resource labels. key app is taken | +| nginx-ingress.ingress.fido2ConfigAdditionalAnnotations | object | `{}` | fido2 config ingress resource additional annotations. | +| nginx-ingress.ingress.fido2ConfigEnabled | bool | `false` | Enable endpoint /.well-known/fido2-configuration | +| nginx-ingress.ingress.fido2ConfigLabels | object | `{}` | fido2 config ingress resource labels. key app is taken | +| nginx-ingress.ingress.openidAdditionalAnnotations | object | `{}` | openid-configuration ingress resource additional annotations. | +| nginx-ingress.ingress.openidConfigEnabled | bool | `true` | Enable endpoint /.well-known/openid-configuration | +| nginx-ingress.ingress.openidConfigLabels | object | `{}` | openid-configuration ingress resource labels. key app is taken | +| nginx-ingress.ingress.scimAdditionalAnnotations | object | `{}` | SCIM ingress resource additional annotations. | +| nginx-ingress.ingress.scimConfigAdditionalAnnotations | object | `{}` | SCIM config ingress resource additional annotations. | +| nginx-ingress.ingress.scimConfigEnabled | bool | `false` | Enable endpoint /.well-known/scim-configuration | +| nginx-ingress.ingress.scimConfigLabels | object | `{}` | SCIM config ingress resource labels. key app is taken | +| nginx-ingress.ingress.scimEnabled | bool | `false` | Enable SCIM endpoints /jans-scim | +| nginx-ingress.ingress.scimLabels | object | `{}` | SCIM config ingress resource labels. key app is taken | +| nginx-ingress.ingress.tls | list | `[{"hosts":["demoexample.gluu.org"],"secretName":"tls-certificate"}]` | Secrets holding HTTPS CA cert and key. | +| nginx-ingress.ingress.u2fAdditionalAnnotations | object | `{}` | u2f config ingress resource additional annotations. | +| nginx-ingress.ingress.u2fConfigEnabled | bool | `true` | Enable endpoint /.well-known/fido-configuration | +| nginx-ingress.ingress.u2fConfigLabels | object | `{}` | u2f config ingress resource labels. key app is taken | +| nginx-ingress.ingress.uma2AdditionalAnnotations | object | `{}` | uma2 config ingress resource additional annotations. | +| nginx-ingress.ingress.uma2ConfigEnabled | bool | `true` | Enable endpoint /.well-known/uma2-configuration | +| nginx-ingress.ingress.uma2ConfigLabels | object | `{}` | uma2 config ingress resource labels. key app is taken | +| nginx-ingress.ingress.webdiscoveryAdditionalAnnotations | object | `{}` | webdiscovery ingress resource additional annotations. | +| nginx-ingress.ingress.webdiscoveryEnabled | bool | `true` | Enable endpoint /.well-known/simple-web-discovery | +| nginx-ingress.ingress.webdiscoveryLabels | object | `{}` | webdiscovery ingress resource labels. key app is taken | +| nginx-ingress.ingress.webfingerAdditionalAnnotations | object | `{}` | webfinger ingress resource additional annotations. | +| nginx-ingress.ingress.webfingerEnabled | bool | `true` | Enable endpoint /.well-known/webfinger | +| nginx-ingress.ingress.webfingerLabels | object | `{}` | webfinger ingress resource labels. key app is taken | +| opendj | object | `{"additionalAnnotations":{},"additionalLabels":{},"backup":{"cronJobSchedule":"*/59 * * * *","enabled":true},"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"gluufederation/opendj","tag":"5.0.0_dev"},"livenessProbe":{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"failureThreshold":20,"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"multiCluster":{"clusterId":"","enabled":false,"namespaceIntId":0,"replicaCount":1,"serfAdvertiseAddrSuffix":"regional.gluu.org:30946","serfKey":"Z51b6PgKU1MZ75NCZOTGGoc0LP2OF3qvF6sjxHyQCYk=","serfPeers":["gluu-opendj-regional-0-regional.gluu.org:30946","gluu-opendj-regional-0-regional.gluu.org:31946"]},"persistence":{"size":"5Gi"},"ports":{"tcp-admin":{"nodePort":"","port":4444,"protocol":"TCP","targetPort":4444},"tcp-ldap":{"nodePort":"","port":1389,"protocol":"TCP","targetPort":1389},"tcp-ldaps":{"nodePort":"","port":1636,"protocol":"TCP","targetPort":1636},"tcp-repl":{"nodePort":"","port":8989,"protocol":"TCP","targetPort":8989},"tcp-serf":{"nodePort":"","port":7946,"protocol":"TCP","targetPort":7946},"udp-serf":{"nodePort":"","port":7946,"protocol":"UDP","targetPort":7946}},"readinessProbe":{"failureThreshold":20,"initialDelaySeconds":60,"periodSeconds":25,"tcpSocket":{"port":1636},"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"1500m","memory":"2000Mi"},"requests":{"cpu":"1500m","memory":"2000Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | OpenDJ is a directory server which implements a wide range of Lightweight Directory Access Protocol and related standards, including full compliance with LDAPv3 but also support for Directory Service Markup Language (DSMLv2).Written in Java, OpenDJ offers multi-master replication, access control, and many extensions. | +| opendj.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| opendj.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| opendj.backup | object | `{"cronJobSchedule":"*/59 * * * *","enabled":true}` | Configure ldap backup cronjob | +| opendj.dnsConfig | object | `{}` | Add custom dns config | +| opendj.dnsPolicy | string | `""` | Add custom dns policy | +| opendj.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | +| opendj.hpa.behavior | object | `{}` | Scaling Policies | +| opendj.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| opendj.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| opendj.image.pullSecrets | list | `[]` | Image Pull Secrets | +| opendj.image.repository | string | `"gluufederation/opendj"` | Image to use for deploying. | +| opendj.image.tag | string | `"5.0.0_dev"` | Image tag to use for deploying. | +| opendj.livenessProbe | object | `{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"failureThreshold":20,"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for OpenDJ if needed. https://github.com/GluuFederation/docker-opendj/blob/master/scripts/healthcheck.py | +| opendj.livenessProbe.exec | object | `{"command":["python3","/app/scripts/healthcheck.py"]}` | Executes the python3 healthcheck. | +| opendj.multiCluster.clusterId | string | `""` | This id needs to be unique to each kubernetes cluster in a multi cluster setup west, east, south, north, region ...etc If left empty it will be randomly generated. | +| opendj.multiCluster.enabled | bool | `false` | Enable OpenDJ multiCluster mode. This flag enables loading keys under `opendj.multiCluster` | +| opendj.multiCluster.namespaceIntId | int | `0` | Namespace int id. This id needs to be a unique number 0-9 per gluu installation per namespace. Used when gluu is installed in the same kubernetes cluster more than once. | +| opendj.multiCluster.replicaCount | int | `1` | The number of opendj non scalabble statefulsets to create. Each pod created must be resolvable as it follows the patterm RELEASE-NAME-opendj-regional-{{statefulset pod number}}-{{ $.Values.multiCluster.serfAdvertiseAddrSuffix }} If set to 1, with a release name of gluu, the address of the pod would be gluu-opendj-regional-0-regional.gluu.org | +| opendj.multiCluster.serfAdvertiseAddrSuffix | string | `"regional.gluu.org:30946"` | OpenDJ Serf advertise address suffix that will be added to each opendj replica. i.e RELEASE-NAME-opendj-regional-{{statefulset pod number}}-{{ $.Values.multiCluster.serfAdvertiseAddrSuffix }} | +| opendj.multiCluster.serfKey | string | `"Z51b6PgKU1MZ75NCZOTGGoc0LP2OF3qvF6sjxHyQCYk="` | Serf key. This key will automatically sync across clusters. | +| opendj.multiCluster.serfPeers | list | `["gluu-opendj-regional-0-regional.gluu.org:30946","gluu-opendj-regional-0-regional.gluu.org:31946"]` | Serf peer addresses. One per cluster. | +| opendj.persistence.size | string | `"5Gi"` | OpenDJ volume size | +| opendj.readinessProbe | object | `{"failureThreshold":20,"initialDelaySeconds":60,"periodSeconds":25,"tcpSocket":{"port":1636},"timeoutSeconds":5}` | Configure the readiness healthcheck for OpenDJ if needed. https://github.com/GluuFederation/docker-opendj/blob/master/scripts/healthcheck.py | +| opendj.replicas | int | `1` | Service replica number. | +| opendj.resources | object | `{"limits":{"cpu":"1500m","memory":"2000Mi"},"requests":{"cpu":"1500m","memory":"2000Mi"}}` | Resource specs. | +| opendj.resources.limits.cpu | string | `"1500m"` | CPU limit. | +| opendj.resources.limits.memory | string | `"2000Mi"` | Memory limit. | +| opendj.resources.requests.cpu | string | `"1500m"` | CPU request. | +| opendj.resources.requests.memory | string | `"2000Mi"` | Memory request. | +| opendj.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| opendj.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| opendj.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| opendj.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| opendj.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| oxpassport | object | `{"additionalAnnotations":{},"additionalLabels":{},"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"gluufederation/oxpassport","tag":"5.0.0_dev"},"livenessProbe":{"failureThreshold":20,"httpGet":{"path":"/passport/health-check","port":"http-passport"},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"readinessProbe":{"failureThreshold":20,"httpGet":{"path":"/passport/health-check","port":"http-passport"},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"700m","memory":"900Mi"},"requests":{"cpu":"700m","memory":"900Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Gluu interface to Passport.js to support social login and inbound identity. | +| oxpassport.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| oxpassport.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| oxpassport.dnsConfig | object | `{}` | Add custom dns config | +| oxpassport.dnsPolicy | string | `""` | Add custom dns policy | +| oxpassport.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | +| oxpassport.hpa.behavior | object | `{}` | Scaling Policies | +| oxpassport.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| oxpassport.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| oxpassport.image.pullSecrets | list | `[]` | Image Pull Secrets | +| oxpassport.image.repository | string | `"gluufederation/oxpassport"` | Image to use for deploying. | +| oxpassport.image.tag | string | `"5.0.0_dev"` | Image tag to use for deploying. | +| oxpassport.livenessProbe | object | `{"failureThreshold":20,"httpGet":{"path":"/passport/health-check","port":"http-passport"},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for oxPassport if needed. | +| oxpassport.livenessProbe.httpGet.path | string | `"/passport/health-check"` | http liveness probe endpoint | +| oxpassport.readinessProbe | object | `{"failureThreshold":20,"httpGet":{"path":"/passport/health-check","port":"http-passport"},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the oxPassport if needed. | +| oxpassport.readinessProbe.httpGet.path | string | `"/passport/health-check"` | http readiness probe endpoint | +| oxpassport.replicas | int | `1` | Service replica number | +| oxpassport.resources | object | `{"limits":{"cpu":"700m","memory":"900Mi"},"requests":{"cpu":"700m","memory":"900Mi"}}` | Resource specs. | +| oxpassport.resources.limits.cpu | string | `"700m"` | CPU limit. | +| oxpassport.resources.limits.memory | string | `"900Mi"` | Memory limit. | +| oxpassport.resources.requests.cpu | string | `"700m"` | CPU request. | +| oxpassport.resources.requests.memory | string | `"900Mi"` | Memory request. | +| oxpassport.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| oxpassport.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| oxpassport.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| oxpassport.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| oxpassport.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| oxshibboleth | object | `{"additionalAnnotations":{},"additionalLabels":{},"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"gluufederation/oxshibboleth","tag":"5.0.0_dev"},"livenessProbe":{"httpGet":{"path":"/idp","port":"http-oxshib"},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"readinessProbe":{"httpGet":{"path":"/idp","port":"http-oxshib"},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"1000m","memory":"1000Mi"},"requests":{"cpu":"1000m","memory":"1000Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Shibboleth project for the Gluu Server's SAML IDP functionality. | +| oxshibboleth.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| oxshibboleth.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| oxshibboleth.dnsConfig | object | `{}` | Add custom dns config | +| oxshibboleth.dnsPolicy | string | `""` | Add custom dns policy | +| oxshibboleth.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | +| oxshibboleth.hpa.behavior | object | `{}` | Scaling Policies | +| oxshibboleth.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| oxshibboleth.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| oxshibboleth.image.pullSecrets | list | `[]` | Image Pull Secrets | +| oxshibboleth.image.repository | string | `"gluufederation/oxshibboleth"` | Image to use for deploying. | +| oxshibboleth.image.tag | string | `"5.0.0_dev"` | Image tag to use for deploying. | +| oxshibboleth.livenessProbe | object | `{"httpGet":{"path":"/idp","port":"http-oxshib"},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for the oxShibboleth if needed. | +| oxshibboleth.livenessProbe.httpGet.path | string | `"/idp"` | http liveness probe endpoint | +| oxshibboleth.readinessProbe | object | `{"httpGet":{"path":"/idp","port":"http-oxshib"},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the casa if needed. | +| oxshibboleth.readinessProbe.httpGet.path | string | `"/idp"` | http liveness probe endpoint | +| oxshibboleth.replicas | int | `1` | Service replica number. | +| oxshibboleth.resources | object | `{"limits":{"cpu":"1000m","memory":"1000Mi"},"requests":{"cpu":"1000m","memory":"1000Mi"}}` | Resource specs. | +| oxshibboleth.resources.limits.cpu | string | `"1000m"` | CPU limit. | +| oxshibboleth.resources.limits.memory | string | `"1000Mi"` | Memory limit. | +| oxshibboleth.resources.requests.cpu | string | `"1000m"` | CPU request. | +| oxshibboleth.resources.requests.memory | string | `"1000Mi"` | Memory request. | +| oxshibboleth.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| oxshibboleth.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| oxshibboleth.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| oxshibboleth.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| oxshibboleth.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| persistence | object | `{"additionalAnnotations":{},"additionalLabels":{},"dnsConfig":{},"dnsPolicy":"","image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"janssenproject/persistence-loader","tag":"1.0.0-beta.14"},"resources":{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Job to generate data and intial config for Gluu Server persistence layer. | +| persistence.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| persistence.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| persistence.dnsConfig | object | `{}` | Add custom dns config | +| persistence.dnsPolicy | string | `""` | Add custom dns policy | +| persistence.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| persistence.image.pullSecrets | list | `[]` | Image Pull Secrets | +| persistence.image.repository | string | `"janssenproject/persistence-loader"` | Image to use for deploying. | +| persistence.image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | +| persistence.resources | object | `{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}}` | Resource specs. | +| persistence.resources.limits.cpu | string | `"300m"` | CPU limit | +| persistence.resources.limits.memory | string | `"300Mi"` | Memory limit. | +| persistence.resources.requests.cpu | string | `"300m"` | CPU request. | +| persistence.resources.requests.memory | string | `"300Mi"` | Memory request. | +| persistence.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| persistence.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| persistence.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| persistence.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| persistence.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| scim | object | `{"additionalAnnotations":{},"additionalLabels":{},"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"janssenproject/scim","tag":"1.0.0-beta.14"},"livenessProbe":{"httpGet":{"path":"/jans-scim/sys/health-check","port":8080},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"readinessProbe":{"httpGet":{"path":"/jans-scim/sys/health-check","port":8080},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"1000m","memory":"1000Mi"},"requests":{"cpu":"1000m","memory":"1000Mi"}},"service":{"name":"http-scim","port":8080},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | System for Cross-domain Identity Management (SCIM) version 2.0 | +| scim.additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| scim.additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| scim.dnsConfig | object | `{}` | Add custom dns config | +| scim.dnsPolicy | string | `""` | Add custom dns policy | +| scim.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | +| scim.hpa.behavior | object | `{}` | Scaling Policies | +| scim.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| scim.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| scim.image.pullSecrets | list | `[]` | Image Pull Secrets | +| scim.image.repository | string | `"janssenproject/scim"` | Image to use for deploying. | +| scim.image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | +| scim.livenessProbe | object | `{"httpGet":{"path":"/jans-scim/sys/health-check","port":8080},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for SCIM if needed. | +| scim.livenessProbe.httpGet.path | string | `"/jans-scim/sys/health-check"` | http liveness probe endpoint | +| scim.readinessProbe | object | `{"httpGet":{"path":"/jans-scim/sys/health-check","port":8080},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the SCIM if needed. | +| scim.readinessProbe.httpGet.path | string | `"/jans-scim/sys/health-check"` | http readiness probe endpoint | +| scim.replicas | int | `1` | Service replica number. | +| scim.resources.limits.cpu | string | `"1000m"` | CPU limit. | +| scim.resources.limits.memory | string | `"1000Mi"` | Memory limit. | +| scim.resources.requests.cpu | string | `"1000m"` | CPU request. | +| scim.resources.requests.memory | string | `"1000Mi"` | Memory request. | +| scim.service.name | string | `"http-scim"` | The name of the scim port within the scim service. Please keep it as default. | +| scim.service.port | int | `8080` | Port of the scim service. Please keep it as default. | +| scim.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| scim.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| scim.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| scim.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| scim.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0) diff --git a/charts/jans/charts/auth-server-key-rotation/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/.helmignore similarity index 100% rename from charts/jans/charts/auth-server-key-rotation/.helmignore rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/.helmignore diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/Chart.yaml new file mode 100644 index 00000000000..1c9eba0828c --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/Chart.yaml @@ -0,0 +1,21 @@ +# All Rights Reserved © 2021 +apiVersion: v2 +name: admin-ui +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" +description: Admin GUI. Requires license. +type: application +keywords: + - Autherization + - OpenID + - GUI +home: https://gluu.org/docs/gluu-server +sources: + - https://github.com/GluuFederation/docker-gluu-admin-ui + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui +maintainers: + - name: Mohammad Abudayyeh + email: support@gluu.org + url: https://github.com/moabu +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: 5.0.0 diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/README.md new file mode 100644 index 00000000000..56d9a9321a6 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/README.md @@ -0,0 +1,61 @@ +# admin-ui + +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) + +Admin GUI. Requires license. + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | + +## Source Code + +* +* + +## Requirements + +Kubernetes: `>=v1.21.0-0` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken | +| additionalLabels | object | `{}` | Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} | +| dnsConfig | object | `{}` | Add custom dns config | +| dnsPolicy | string | `""` | Add custom dns policy | +| hpa.behavior | object | `{}` | Scaling Policies | +| hpa.enabled | bool | `true` | | +| hpa.maxReplicas | int | `10` | | +| hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| hpa.minReplicas | int | `1` | | +| hpa.targetCPUUtilizationPercentage | int | `50` | | +| image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| image.pullSecrets | list | `[]` | Image Pull Secrets | +| image.repository | string | `"gluufederation/admin-ui"` | Image to use for deploying. | +| image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | +| livenessProbe | object | `{"failureThreshold":20,"initialDelaySeconds":60,"periodSeconds":25,"tcpSocket":{"port":1636},"timeoutSeconds":5}` | Configure the liveness healthcheck for the admin ui if needed. | +| readinessProbe | object | `{"failureThreshold":20,"initialDelaySeconds":60,"periodSeconds":25,"tcpSocket":{"port":1636},"timeoutSeconds":5}` | Configure the readiness healthcheck for the admin ui if needed. | +| replicas | int | `1` | Service replica number. | +| resources | object | `{"limits":{"cpu":"2500m","memory":"2500Mi"},"requests":{"cpu":"2500m","memory":"2500Mi"}}` | Resource specs. | +| resources.limits.cpu | string | `"2500m"` | CPU limit. | +| resources.limits.memory | string | `"2500Mi"` | Memory limit. | +| resources.requests.cpu | string | `"2500m"` | CPU request. | +| resources.requests.memory | string | `"2500Mi"` | Memory request. | +| service.name | string | `"http-admin-ui"` | The name of the admin ui port within the admin service. Please keep it as default. | +| service.port | int | `8080` | Port of the admin ui service. Please keep it as default. | +| service.sessionAffinity | string | `"None"` | Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP | +| service.sessionAffinityConfig | object | `{"clientIP":{"timeoutSeconds":10800}}` | the maximum session sticky time if sessionAffinity is ClientIP | +| usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| volumes | list | `[]` | | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0) diff --git a/charts/jans/charts/config/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/_helpers.tpl similarity index 83% rename from charts/jans/charts/config/templates/_helpers.tpl rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/_helpers.tpl index 83005bb3571..27e0aa19246 100644 --- a/charts/jans/charts/config/templates/_helpers.tpl +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/_helpers.tpl @@ -2,7 +2,7 @@ {{/* Expand the name of the chart. */}} -{{- define "config.name" -}} +{{- define "admin-ui.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} @@ -11,7 +11,7 @@ Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} -{{- define "config.fullname" -}} +{{- define "admin-ui.fullname" -}} {{- if .Values.fullnameOverride -}} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} {{- else -}} @@ -27,16 +27,16 @@ If release name contains chart name it will be used as a full name. {{/* Create chart name and version as used by the chart label. */}} -{{- define "config.chart" -}} +{{- define "admin-ui.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Common labels */}} -{{- define "config.labels" -}} -app: {{ .Release.Name }}-{{ include "config.name" . }}-init-load -helm.sh/chart: {{ include "config.chart" . }} +{{- define "admin-ui.labels" -}} +app: {{ .Release.Name }}-{{ include "admin-ui.name" . }} +helm.sh/chart: {{ include "admin-ui.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} @@ -47,7 +47,7 @@ app.kubernetes.io/managed-by: {{ .Release.Service }} {{/* Create user custom defined envs */}} -{{- define "config.usr-envs"}} +{{- define "admin-ui.usr-envs"}} {{- range $key, $val := .Values.usrEnvs.normal }} - name: {{ $key }} value: {{ $val }} @@ -57,7 +57,7 @@ Create user custom defined envs {{/* Create user custom defined secret envs */}} -{{- define "config.usr-secret-envs"}} +{{- define "admin-ui.usr-secret-envs"}} {{- range $key, $val := .Values.usrEnvs.secret }} - name: {{ $key }} valueFrom: diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/admin-ui-destination-rules.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/admin-ui-destination-rules.yaml new file mode 100644 index 00000000000..6643bee6614 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/admin-ui-destination-rules.yaml @@ -0,0 +1,23 @@ +{{- if .Values.global.istio.enabled }} +# All Rights Reserved © 2021 +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: {{ .Release.Name }}-admin-ui-mtls + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: admin-ui +{{ include "admin-ui.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + host: {{ index .Values "global" "admin-ui" "adminUiServiceName" }}.{{ .Release.Namespace }}.svc.cluster.local + trafficPolicy: + tls: + mode: ISTIO_MUTUAL +{{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/admin-ui-virtual-services.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/admin-ui-virtual-services.yaml new file mode 100644 index 00000000000..ce044cd007e --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/admin-ui-virtual-services.yaml @@ -0,0 +1,33 @@ +{{- if .Values.global.istio.enabled }} +# All Rights Reserved © 2021 +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: {{ .Release.Name }}-istio-admin-ui + namespace: {{.Release.Namespace}} + labels: + APP_NAME: admin-ui +{{ include "admin-ui.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + hosts: + - {{ .Values.global.fqdn }} + gateways: + - {{ .Release.Name }}-global-gtw # can omit the namespace if gateway is in same namespace as virtual service. + http: + - name: "{{ .Release.Name }}-istio-cn" + match: + - uri: + prefix: "/admin" + route: + - destination: + host: {{ index .Values "global" "admin-ui" "adminUiServiceName" }}.{{ .Release.Namespace }}.svc.cluster.local + port: + number: 8080 +{{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/deployment.yml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/deployment.yml new file mode 100644 index 00000000000..9a5995ce8fd --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/deployment.yml @@ -0,0 +1,143 @@ +# All Rights Reserved © 2021 +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "admin-ui.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: admin-ui +{{ include "admin-ui.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + replicas: {{ .Values.replicas }} + selector: + matchLabels: + app: {{ .Release.Name }}-{{ include "admin-ui.name" . }} + template: + metadata: + labels: + APP_NAME: admin-ui + app: {{ .Release.Name }}-{{ include "admin-ui.name" . }} + {{- if .Values.global.istio.ingress }} + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + {{- end }} + spec: + {{- with .Values.image.pullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + dnsPolicy: {{ .Values.dnsPolicy | quote }} + {{- with .Values.dnsConfig }} + dnsConfig: +{{ toYaml . | indent 8 }} + {{- end }} + containers: + - name: {{ include "admin-ui.name" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + image: {{ .Values.image.repository }}:{{ .Values.image.tag }} + env: + {{- include "admin-ui.usr-envs" . | indent 12 }} + {{- include "admin-ui.usr-secret-envs" . | indent 12 }} + securityContext: + runAsUser: 1000 + runAsNonRoot: true + {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} + command: + - /bin/sh + - -c + - | + /usr/bin/python3 /scripts/updatelbip.py & + /app/scripts/entrypoint.sh + {{- end}} + ports: + - name: {{ .Values.service.name }} + containerPort: {{ .Values.service.port }} + envFrom: + - configMapRef: + name: {{ .Release.Name }}-config-cm + {{ if .Values.global.usrEnvs.secret }} + - secretRef: + name: {{ .Release.Name }}-global-user-custom-envs + {{- end }} + {{ if .Values.global.usrEnvs.normal }} + - configMapRef: + name: {{ .Release.Name }}-global-user-custom-envs + {{- end }} + volumeMounts: + {{- with .Values.volumeMounts }} +{{- toYaml . | nindent 10 }} + {{- end }} + {{- if .Values.global.jackrabbit.enabled }} + - name: cn-jackrabbit-admin-pass + mountPath: /etc/gluu/conf/jackrabbit_admin_password + subPath: jackrabbit_admin_password + {{- end }} + {{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} + - mountPath: {{ .Values.global.cnGoogleApplicationCredentials }} + name: google-sa + subPath: google-credentials.json + {{- end }} + {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} + - name: {{ include "admin-ui.fullname" .}}-updatelbip + mountPath: "/scripts" + {{- end }} + + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} + {{- if not .Values.global.istio.enabled }} + - name: cb-crt + mountPath: "/etc/certs/couchbase.crt" + subPath: couchbase.crt + {{- end }} + {{- end }} + livenessProbe: +{{- toYaml .Values.livenessProbe | nindent 10 }} + readinessProbe: +{{- toYaml .Values.readinessProbe | nindent 10 }} + {{- if or (eq .Values.global.storageClass.provisioner "microk8s.io/hostpath" ) (eq .Values.global.storageClass.provisioner "k8s.io/minikube-hostpath") }} + resources: {} + {{- else if .Values.global.cloud.testEnviroment }} + resources: {} + {{- else }} + resources: +{{- toYaml .Values.resources | nindent 10 }} + {{- end }} + {{- if not .Values.global.isFqdnRegistered }} + hostAliases: + - ip: {{ .Values.global.lbIp }} + hostnames: + - {{ .Values.global.fqdn }} + {{- end }} + volumes: + {{- with .Values.volumes }} +{{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.global.jackrabbit.enabled }} + - name: cn-jackrabbit-admin-pass + secret: + secretName: cn-jackrabbit-admin-pass + {{- end }} + {{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} + - name: google-sa + secret: + secretName: {{ .Release.Name }}-google-sa + {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} + {{- if not .Values.global.istio.enabled }} + - name: cb-crt + secret: + secretName: {{ .Release.Name }}-cb-crt + {{- end }} + {{- end }} + {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} + - name: {{ include "admin-ui.fullname" . }}-updatelbip + configMap: + name: {{ .Release.Name }}-updatelbip + {{- end }} + \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/hpa.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/hpa.yaml new file mode 100644 index 00000000000..9b620839fff --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/hpa.yaml @@ -0,0 +1,38 @@ +{{ if .Values.hpa.enabled -}} +# All Rights Reserved © 2021 +apiVersion: autoscaling/v1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "admin-ui.fullname" . }} + labels: + APP_NAME: admin-ui +{{ include "admin-ui.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "admin-ui.fullname" . }} + minReplicas: {{ .Values.hpa.minReplicas }} + maxReplicas: {{ .Values.hpa.maxReplicas }} + {{- if .Values.hpa.targetCPUUtilizationPercentage }} + targetCPUUtilizationPercentage: {{ .Values.hpa.targetCPUUtilizationPercentage }} + {{- else if .Values.hpa.metrics }} + metrics: + {{- with .Values.hpa.metrics }} +{{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} + {{- if .Values.hpa.behavior }} + behavior: + {{- with .Values.hpa.behavior }} +{{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/service.yml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/service.yml new file mode 100644 index 00000000000..2cb02f0ebc2 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/service.yml @@ -0,0 +1,30 @@ +# All Rights Reserved © 2021 +apiVersion: v1 +kind: Service +metadata: + name: {{ index .Values "global" "admin-ui" "adminUiServiceName" }} + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: admin-ui +{{ include "admin-ui.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + {{- if .Values.global.alb.ingress }} + type: NodePort + {{- end }} + ports: + - port: {{ .Values.service.port }} + name: {{ .Values.service.name }} + selector: + app: {{ .Release.Name }}-{{ include "admin-ui.name" . }} #admin-ui + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- with .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: +{{ toYaml . | indent 4 }} + {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/user-custom-secret-envs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/user-custom-secret-envs.yaml new file mode 100644 index 00000000000..95a833ca051 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/templates/user-custom-secret-envs.yaml @@ -0,0 +1,22 @@ +{{ if .Values.usrEnvs.secret }} +# All Rights Reserved © 2021 +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs + labels: + APP_NAME: admin-ui +{{ include "admin-ui.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/values.yaml new file mode 100644 index 00000000000..7a98d57ebfd --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/admin-ui/values.yaml @@ -0,0 +1,82 @@ +# All Rights Reserved © 2021 +# -- Admin GUI. Requires license. +# -- Configure the HorizontalPodAutoscaler +hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} +# -- Add custom normal and secret envs to the service +usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} +# -- Add custom dns policy +dnsPolicy: "" +# -- Add custom dns config +dnsConfig: {} +image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: gluufederation/admin-ui + # -- Image tag to use for deploying. + tag: 1.0.0-beta.14 + # -- Image Pull Secrets + pullSecrets: [ ] +# -- Service replica number. +replicas: 1 +# -- Resource specs. +resources: + limits: + # -- CPU limit. + cpu: 2500m + # -- Memory limit. + memory: 2500Mi + requests: + # -- CPU request. + cpu: 2500m + # -- Memory request. + memory: 2500Mi +service: + # -- The name of the admin ui port within the admin service. Please keep it as default. + name: http-admin-ui + # -- Port of the admin ui service. Please keep it as default. + port: 8080 + # -- Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP + sessionAffinity: None + # -- the maximum session sticky time if sessionAffinity is ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: 10800 +# -- Configure the liveness healthcheck for the admin ui if needed. +livenessProbe: + tcpSocket: + port: 1636 + initialDelaySeconds: 60 + timeoutSeconds: 5 + periodSeconds: 25 + failureThreshold: 20 +# -- Configure the readiness healthcheck for the admin ui if needed. +readinessProbe: + tcpSocket: + port: 1636 + initialDelaySeconds: 60 + timeoutSeconds: 5 + periodSeconds: 25 + failureThreshold: 20 +volumes: [] +# -- Configure any additional volumesMounts that need to be attached to the containers +volumeMounts: [] + +# -- Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken +additionalAnnotations: { } diff --git a/charts/jans/charts/auth-server/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/.helmignore similarity index 100% rename from charts/jans/charts/auth-server/.helmignore rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/.helmignore diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/Chart.yaml new file mode 100644 index 00000000000..c3462cd074f --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/Chart.yaml @@ -0,0 +1,20 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v2 +name: auth-server-key-rotation +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" +description: Responsible for regenerating auth-keys per x hours +type: application +keywords: + - Auth keys Rotation +home: https://gluu.org/docs/gluu-server +sources: + - https://github.com/JanssenProject/docker-jans-certmanager + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation +maintainers: + - name: Mohammad Abudayyeh + email: support@gluu.org + url: https://github.com/moabu +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: "5.0.0" \ No newline at end of file diff --git a/charts/jans/charts/auth-server-key-rotation/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/README.md similarity index 60% rename from charts/jans/charts/auth-server-key-rotation/README.md rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/README.md index 51050758b9b..8a5ea814b41 100644 --- a/charts/jans/charts/auth-server-key-rotation/README.md +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/README.md @@ -1,37 +1,39 @@ # auth-server-key-rotation -![Version: 1.0.0-b11](https://img.shields.io/badge/Version-1.0.0--b11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-b11](https://img.shields.io/badge/AppVersion-1.0.0--b11-informational?style=flat-square) +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) Responsible for regenerating auth-keys per x hours -**Homepage:** +**Homepage:** ## Maintainers | Name | Email | Url | | ---- | ------ | --- | -| Mohammad Abudayyeh | support@jans.io | https://github.com/moabu | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | ## Source Code * -* +* ## Requirements -Kubernetes: `>=v1.19.0-0` +Kubernetes: `>=v1.21.0-0` ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken | +| additionalLabels | object | `{}` | Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} | | affinity | object | `{}` | | | dnsConfig | object | `{}` | Add custom dns config | | dnsPolicy | string | `""` | Add custom dns policy | | image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | | image.pullSecrets | list | `[]` | Image Pull Secrets | | image.repository | string | `"janssenproject/certmanager"` | Image to use for deploying. | -| image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | +| image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | | keysLife | int | `48` | Auth server key rotation keys life in hours | | nodeSelector | object | `{}` | | | resources | object | `{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}}` | Resource specs. | diff --git a/charts/jans/charts/auth-server-key-rotation/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/templates/_helpers.tpl similarity index 100% rename from charts/jans/charts/auth-server-key-rotation/templates/_helpers.tpl rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/templates/_helpers.tpl diff --git a/charts/jans/charts/auth-server-key-rotation/templates/cronjobs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/templates/cronjobs.yaml similarity index 83% rename from charts/jans/charts/auth-server-key-rotation/templates/cronjobs.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/templates/cronjobs.yaml index 1a1a028b551..a3436b8a5ed 100644 --- a/charts/jans/charts/auth-server-key-rotation/templates/cronjobs.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/templates/cronjobs.yaml @@ -1,4 +1,4 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 kind: CronJob apiVersion: batch/v1beta1 @@ -9,6 +9,13 @@ metadata: APP_NAME: auth-server-key-rotation release: {{ .Release.Name }} {{ include "auth-server-key-rotation.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: schedule: "0 */{{ .Values.keysLife }} * * *" concurrencyPolicy: Forbid @@ -41,18 +48,10 @@ spec: name: google-sa subPath: google-credentials.json {{- end }} - {{- if eq .Values.global.cnPersistenceType "sql" }} - - name: sql-pass - mountPath: "/etc/jans/conf/sql_password" - subPath: sql_password - {{- end }} {{- with .Values.volumeMounts }} {{- toYaml . | nindent 16 }} {{- end }} {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} - - name: cb-pass - mountPath: "/etc/jans/conf/couchbase_password" - subPath: couchbase_password {{- if not .Values.global.istio.enabled }} - name: cb-crt mountPath: "/etc/certs/couchbase.crt" @@ -88,15 +87,7 @@ spec: secret: secretName: {{ .Release.Name }}-google-sa {{- end }} - {{- if eq .Values.global.cnPersistenceType "sql" }} - - name: sql-pass - secret: - secretName: {{ .Release.Name }}-sql-pass - {{- end }} {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} - - name: cb-pass - secret: - secretName: {{ .Release.Name }}-cb-pass {{- if not .Values.global.istio.enabled }} - name: cb-crt secret: diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/templates/service.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/templates/service.yaml new file mode 100644 index 00000000000..4b1f6ff0762 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/templates/service.yaml @@ -0,0 +1,25 @@ +{{- if .Values.global.istio.enabled }} +# License terms and conditions: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Service +metadata: + name: {{ include "auth-server-key-rotation.fullname" . }} + labels: +{{ include "auth-server-key-rotation.fullname" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + ports: + - name: http + port: 80 + targetPort: 8080 + selector: + app: {{ .Release.Name }}-{{ include "auth-server-key-rotation.name" . }} + type: ClusterIP +{{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/templates/user-custom-secret-envs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/templates/user-custom-secret-envs.yaml new file mode 100644 index 00000000000..187d0948f97 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/templates/user-custom-secret-envs.yaml @@ -0,0 +1,22 @@ +{{ if .Values.usrEnvs.secret }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs + labels: +{{ include "auth-server-key-rotation.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} \ No newline at end of file diff --git a/charts/jans/charts/auth-server-key-rotation/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/values.yaml similarity index 72% rename from charts/jans/charts/auth-server-key-rotation/values.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/values.yaml index 223c6354ee8..d0a688239a4 100644 --- a/charts/jans/charts/auth-server-key-rotation/values.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server-key-rotation/values.yaml @@ -1,4 +1,4 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 # -- Responsible for regenerating auth-keys per x hours # -- Add custom normal and secret envs to the service @@ -19,7 +19,7 @@ image: # -- Image to use for deploying. repository: janssenproject/certmanager # -- Image tag to use for deploying. - tag: 1.0.0_b11 + tag: 1.0.0-beta.14 # -- Image Pull Secrets pullSecrets: [ ] # -- Auth server key rotation keys life in hours @@ -41,4 +41,9 @@ nodeSelector: {} tolerations: [] -affinity: {} \ No newline at end of file +affinity: {} + +# -- Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken +additionalAnnotations: { } \ No newline at end of file diff --git a/charts/jans/charts/client-api/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/.helmignore similarity index 100% rename from charts/jans/charts/client-api/.helmignore rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/.helmignore diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/Chart.yaml new file mode 100644 index 00000000000..95d4d18142f --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/Chart.yaml @@ -0,0 +1,22 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v2 +name: auth-server +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" +description: OAuth Authorization Server, the OpenID Connect Provider, the UMA Authorization Server--this is the main Internet facing component of Gluu. It's the service that returns tokens, JWT's and identity assertions. This service must be Internet facing. +type: application +keywords: + - Autherization + - OpenID +home: https://gluu.org/docs/gluu-server +sources: + - https://github.com/JanssenProject/jans-auth-server + - https://github.com/JanssenProject/docker-jans-auth-server + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/auth-server +maintainers: + - name: Mohammad Abudayyeh + email: support@gluu.org + url: https://github.com/moabu +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: 5.0.0 diff --git a/charts/jans/charts/auth-server/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/README.md similarity index 62% rename from charts/jans/charts/auth-server/README.md rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/README.md index 89f8cbe7e80..6e27a1ce639 100644 --- a/charts/jans/charts/auth-server/README.md +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/README.md @@ -1,31 +1,33 @@ # auth-server -![Version: 1.0.0-b11](https://img.shields.io/badge/Version-1.0.0--b11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-b11](https://img.shields.io/badge/AppVersion-1.0.0--b11-informational?style=flat-square) +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) -OAuth Authorization Server, the OpenID Connect Provider, the UMA Authorization Server--this is the main Internet facing component of Janssen. It's the service that returns tokens, JWT's and identity assertions. This service must be Internet facing. +OAuth Authorization Server, the OpenID Connect Provider, the UMA Authorization Server--this is the main Internet facing component of Gluu. It's the service that returns tokens, JWT's and identity assertions. This service must be Internet facing. -**Homepage:** +**Homepage:** ## Maintainers | Name | Email | Url | | ---- | ------ | --- | -| Mohammad Abudayyeh | support@jans.io | https://github.com/moabu | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | ## Source Code * * -* +* ## Requirements -Kubernetes: `>=v1.19.0-0` +Kubernetes: `>=v1.21.0-0` ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken | +| additionalLabels | object | `{}` | Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} | | dnsConfig | object | `{}` | Add custom dns config | | dnsPolicy | string | `""` | Add custom dns policy | | hpa.behavior | object | `{}` | Scaling Policies | @@ -37,10 +39,10 @@ Kubernetes: `>=v1.19.0-0` | image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | | image.pullSecrets | list | `[]` | Image Pull Secrets | | image.repository | string | `"janssenproject/auth-server"` | Image to use for deploying. | -| image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | +| image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | | livenessProbe | object | `{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for the auth server if needed. | -| livenessProbe.exec | object | `{"command":["python3","/app/scripts/healthcheck.py"]}` | Executes the python3 healthcheck. https://github.com/JanssenFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py | -| readinessProbe | object | `{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the auth server if needed. https://github.com/JanssenFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py | +| livenessProbe.exec | object | `{"command":["python3","/app/scripts/healthcheck.py"]}` | Executes the python3 healthcheck. https://github.com/GluuFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py | +| readinessProbe | object | `{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the auth server if needed. https://github.com/GluuFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py | | replicas | int | `1` | Service replica number. | | resources | object | `{"limits":{"cpu":"2500m","memory":"2500Mi"},"requests":{"cpu":"2500m","memory":"2500Mi"}}` | Resource specs. | | resources.limits.cpu | string | `"2500m"` | CPU limit. | @@ -49,6 +51,8 @@ Kubernetes: `>=v1.19.0-0` | resources.requests.memory | string | `"2500Mi"` | Memory request. | | service.name | string | `"http-auth"` | The name of the oxauth port within the oxauth service. Please keep it as default. | | service.port | int | `8080` | Port of the oxauth service. Please keep it as default. | +| service.sessionAffinity | string | `"None"` | Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP | +| service.sessionAffinityConfig | object | `{"clientIP":{"timeoutSeconds":10800}}` | the maximum session sticky time if sessionAffinity is ClientIP | | usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | | usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | | usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | diff --git a/charts/jans/charts/auth-server/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/_helpers.tpl similarity index 100% rename from charts/jans/charts/auth-server/templates/_helpers.tpl rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/_helpers.tpl diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/auth-server-destination-rules.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/auth-server-destination-rules.yaml new file mode 100644 index 00000000000..4c83973a7f5 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/auth-server-destination-rules.yaml @@ -0,0 +1,24 @@ +{{- if .Values.global.istio.enabled }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: {{ .Release.Name }}-auth-server-mtls + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: auth-server +{{ include "auth-server.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + host: {{ index .Values "global" "auth-server" "authServerServiceName" }}.{{ .Release.Namespace }}.svc.cluster.local + trafficPolicy: + tls: + mode: ISTIO_MUTUAL +{{- end }} \ No newline at end of file diff --git a/charts/jans/charts/auth-server/templates/auth-server-virtual-services.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/auth-server-virtual-services.yaml similarity index 87% rename from charts/jans/charts/auth-server/templates/auth-server-virtual-services.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/auth-server-virtual-services.yaml index 164a32ca47a..0a88915702f 100644 --- a/charts/jans/charts/auth-server/templates/auth-server-virtual-services.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/auth-server-virtual-services.yaml @@ -1,11 +1,21 @@ {{- if .Values.global.istio.enabled }} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: {{ .Release.Name }}-istio-auth-server namespace: {{.Release.Namespace}} + labels: + APP_NAME: auth-server +{{ include "auth-server.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: hosts: - {{ .Values.global.fqdn }} diff --git a/charts/jans/charts/auth-server/templates/deployment.yml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/deployment.yml similarity index 93% rename from charts/jans/charts/auth-server/templates/deployment.yml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/deployment.yml index 5959354a896..d05ad6905e3 100644 --- a/charts/jans/charts/auth-server/templates/deployment.yml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/deployment.yml @@ -1,4 +1,4 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: apps/v1 kind: Deployment @@ -8,6 +8,13 @@ metadata: labels: APP_NAME: auth-server {{ include "auth-server.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: replicas: {{ .Values.replicas }} selector: @@ -103,6 +110,11 @@ spec: mountPath: /etc/certs/ob-transport-truststore.p12 subPath: ob-transport-truststore.p12 {{- end }} + {{- if .Values.global.jackrabbit.enabled }} + - name: cn-jackrabbit-admin-pass + mountPath: /etc/gluu/conf/jackrabbit_admin_password + subPath: jackrabbit_admin_password + {{- end }} {{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} - mountPath: {{ .Values.global.cnGoogleApplicationCredentials }} name: google-sa @@ -112,15 +124,9 @@ spec: - name: {{ include "auth-server.fullname" .}}-updatelbip mountPath: "/scripts" {{- end }} - {{- if eq .Values.global.cnPersistenceType "sql" }} - - name: sql-pass - mountPath: "/etc/jans/conf/sql_password" - subPath: sql_password - {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} - - name: cb-pass - mountPath: "/etc/jans/conf/couchbase_password" - subPath: couchbase_password + {{- if not .Values.global.istio.enabled }} - name: cb-crt mountPath: "/etc/certs/couchbase.crt" @@ -202,20 +208,19 @@ spec: secret: secretName: {{ .Release.Name }}-ob-transport-truststore {{- end }} + {{- if .Values.global.jackrabbit.enabled }} + - name: cn-jackrabbit-admin-pass + secret: + secretName: cn-jackrabbit-admin-pass + {{- end }} {{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} - name: google-sa secret: secretName: {{ .Release.Name }}-google-sa {{- end }} - {{- if eq .Values.global.cnPersistenceType "sql" }} - - name: sql-pass - secret: - secretName: {{ .Release.Name }}-sql-pass - {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} - - name: cb-pass - secret: - secretName: {{ .Release.Name }}-cb-pass + {{- if not .Values.global.istio.enabled }} - name: cb-crt secret: diff --git a/charts/jans/charts/auth-server/templates/hpa.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/hpa.yaml similarity index 68% rename from charts/jans/charts/auth-server/templates/hpa.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/hpa.yaml index c94608decf4..859a3986ed9 100644 --- a/charts/jans/charts/auth-server/templates/hpa.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/hpa.yaml @@ -1,10 +1,20 @@ {{ if .Values.hpa.enabled -}} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: {{ include "auth-server.fullname" . }} + labels: + APP_NAME: auth-server +{{ include "auth-server.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: scaleTargetRef: apiVersion: apps/v1 diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/service.yml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/service.yml new file mode 100644 index 00000000000..c5a83f91d5b --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/service.yml @@ -0,0 +1,31 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Service +metadata: + name: {{ index .Values "global" "auth-server" "authServerServiceName" }} + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: auth-server +{{ include "auth-server.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + {{- if .Values.global.alb.ingress }} + type: NodePort + {{- end }} + ports: + - port: {{ .Values.service.port }} + name: {{ .Values.service.name }} + selector: + app: {{ .Release.Name }}-{{ include "auth-server.name" . }} #auth-server + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- with .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: +{{ toYaml . | indent 4 }} + {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/user-custom-secret-envs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/user-custom-secret-envs.yaml new file mode 100644 index 00000000000..1903a4f60dc --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/templates/user-custom-secret-envs.yaml @@ -0,0 +1,23 @@ +{{ if .Values.usrEnvs.secret }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs + labels: + APP_NAME: auth-server +{{ include "auth-server.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} \ No newline at end of file diff --git a/charts/jans/charts/auth-server/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/values.yaml similarity index 65% rename from charts/jans/charts/auth-server/values.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/values.yaml index 4998532e364..bc35f36eed1 100644 --- a/charts/jans/charts/auth-server/values.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/auth-server/values.yaml @@ -1,6 +1,6 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 -# -- OAuth Authorization Server, the OpenID Connect Provider, the UMA Authorization Server--this is the main Internet facing component of Janssen. It's the service that returns tokens, JWT's and identity assertions. This service must be Internet facing. +# -- OAuth Authorization Server, the OpenID Connect Provider, the UMA Authorization Server--this is the main Internet facing component of Gluu. It's the service that returns tokens, JWT's and identity assertions. This service must be Internet facing. # -- Configure the HorizontalPodAutoscaler hpa: enabled: true @@ -29,7 +29,7 @@ image: # -- Image to use for deploying. repository: janssenproject/auth-server # -- Image tag to use for deploying. - tag: 1.0.0_b11 + tag: 1.0.0-beta.14 # -- Image Pull Secrets pullSecrets: [ ] # -- Service replica number. @@ -51,10 +51,16 @@ service: name: http-auth # -- Port of the oxauth service. Please keep it as default. port: 8080 + # -- Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP + sessionAffinity: None + # -- the maximum session sticky time if sessionAffinity is ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: 10800 # -- Configure the liveness healthcheck for the auth server if needed. livenessProbe: # -- Executes the python3 healthcheck. - # https://github.com/JanssenFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py + # https://github.com/GluuFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py exec: command: - python3 @@ -63,7 +69,7 @@ livenessProbe: periodSeconds: 30 timeoutSeconds: 5 # -- Configure the readiness healthcheck for the auth server if needed. -# https://github.com/JanssenFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py +# https://github.com/GluuFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py readinessProbe: exec: command: @@ -75,3 +81,8 @@ readinessProbe: volumes: [] # -- Configure any additional volumesMounts that need to be attached to the containers volumeMounts: [] + +# -- Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken +additionalAnnotations: { } diff --git a/charts/jans/charts/cn-istio-ingress/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/.helmignore similarity index 100% rename from charts/jans/charts/cn-istio-ingress/.helmignore rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/.helmignore diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/Chart.yaml new file mode 100644 index 00000000000..57f9e6cb8a4 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/Chart.yaml @@ -0,0 +1,23 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v2 +name: casa +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" +description: Gluu Casa ("Casa") is a self-service web portal for end-users to manage authentication and authorization preferences for their account in a Gluu Server. +type: application +keywords: + - casa + - 2FA + - passwordless +home: https://gluu.org/docs/casa/ +sources: + - https://gluu.org/docs/casa/ + - https://github.com/GluuFederation/docker-casa + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/casa +maintainers: + - name: Mohammad Abudayyeh + email: support@gluu.org + url: https://github.com/moabu +icon: https://casa.gluu.org/wp-content/themes/gluucasa/casafavicon.ico +appVersion: "5.0.0" diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/README.md new file mode 100644 index 00000000000..350f103557c --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/README.md @@ -0,0 +1,68 @@ +# casa + +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) + +Gluu Casa ("Casa") is a self-service web portal for end-users to manage authentication and authorization preferences for their account in a Gluu Server. + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | + +## Source Code + +* +* +* + +## Requirements + +Kubernetes: `>=v1.21.0-0` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken | +| additionalLabels | object | `{}` | Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} | +| dnsConfig | object | `{}` | Add custom dns config | +| dnsPolicy | string | `""` | Add custom dns policy | +| fullnameOverride | string | `""` | | +| hpa.behavior | object | `{}` | Scaling Policies | +| hpa.enabled | bool | `true` | | +| hpa.maxReplicas | int | `10` | | +| hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| hpa.minReplicas | int | `1` | | +| hpa.targetCPUUtilizationPercentage | int | `50` | | +| image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| image.pullSecrets | list | `[]` | Image Pull Secrets | +| image.repository | string | `"gluufederation/casa"` | Image to use for deploying. | +| image.tag | string | `"5.0.0_dev"` | Image tag to use for deploying. | +| livenessProbe | object | `{"httpGet":{"path":"/casa/health-check","port":"http-casa"},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the liveness healthcheck for casa if needed. | +| livenessProbe.httpGet.path | string | `"/casa/health-check"` | http liveness probe endpoint | +| nameOverride | string | `""` | | +| podSecurityContext | object | `{}` | | +| readinessProbe | object | `{"httpGet":{"path":"/casa/health-check","port":"http-casa"},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the readiness healthcheck for the casa if needed. | +| readinessProbe.httpGet.path | string | `"/casa/health-check"` | http readiness probe endpoint | +| replicas | int | `1` | Service replica number. | +| resources | object | `{"limits":{"cpu":"500m","memory":"500Mi"},"requests":{"cpu":"500m","memory":"500Mi"}}` | Resource specs. | +| resources.limits.cpu | string | `"500m"` | CPU limit. | +| resources.limits.memory | string | `"500Mi"` | Memory limit. | +| resources.requests.cpu | string | `"500m"` | CPU request. | +| resources.requests.memory | string | `"500Mi"` | Memory request. | +| securityContext | object | `{}` | | +| service.name | string | `"http-casa"` | The name of the casa port within the casa service. Please keep it as default. | +| service.port | int | `8080` | Port of the casa service. Please keep it as default. | +| service.sessionAffinity | string | `"None"` | Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP | +| service.sessionAffinityConfig | object | `{"clientIP":{"timeoutSeconds":10800}}` | the maximum session sticky time if sessionAffinity is ClientIP | +| usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0) diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/_helpers.tpl new file mode 100644 index 00000000000..07d38cacf81 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/_helpers.tpl @@ -0,0 +1,79 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "casa.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "casa.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "casa.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "casa.labels" -}} +app: {{ .Release.Name }}-{{ include "casa.name" . }} +helm.sh/chart: {{ include "casa.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "casa.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "casa.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create user custom defined envs +*/}} +{{- define "casa.usr-envs"}} +{{- range $key, $val := .Values.usrEnvs.normal }} +- name: {{ $key }} + value: {{ $val }} +{{- end }} +{{- end }} + +{{/* +Create user custom defined secret envs +*/}} +{{- define "casa.usr-secret-envs"}} +{{- range $key, $val := .Values.usrEnvs.secret }} +- name: {{ $key }} + valueFrom: + secretKeyRef: + name: {{ $.Release.Name }}-{{ $.Chart.Name }}-user-custom-envs + key: {{ $key }} +{{- end }} +{{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/casa-destination-rules.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/casa-destination-rules.yaml new file mode 100644 index 00000000000..1bab638b870 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/casa-destination-rules.yaml @@ -0,0 +1,24 @@ +{{- if .Values.global.istio.enabled }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: {{ .Release.Name }}-casa-mtls + namespace: {{.Release.Namespace}} + labels: + APP_NAME: casa +{{ include "casa.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + host: {{ .Values.global.casa.casaServiceName }}.{{ .Release.Namespace }}.svc.cluster.local + trafficPolicy: + tls: + mode: ISTIO_MUTUAL +{{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/casa-virtual-services.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/casa-virtual-services.yaml new file mode 100644 index 00000000000..066009a6833 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/casa-virtual-services.yaml @@ -0,0 +1,35 @@ +{{- if .Values.global.istio.ingress }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: {{ .Release.Name }}-istio-casa + namespace: {{.Release.Namespace}} + labels: + APP_NAME: casa +{{ include "casa.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + gateways: + - {{ .Release.Name }}-global-gtw + hosts: + - {{ .Values.global.fqdn }} + http: + - name: {{ .Release.Name }}-istio-casa + match: + - uri: + exact: /casa + route: + - destination: + host: {{ .Values.global.casa.casaServiceName }}.{{.Release.Namespace}}.svc.cluster.local + port: + number: 8080 + weight: 100 +{{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/deployment.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/deployment.yaml new file mode 100644 index 00000000000..466e29812db --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/deployment.yaml @@ -0,0 +1,150 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "casa.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: casa +{{ include "casa.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + replicas: {{ .Values.replicas }} + selector: + matchLabels: + app: {{ .Release.Name }}-{{ include "casa.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + APP_NAME: casa + app: {{ .Release.Name }}-{{ include "casa.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + {{- if .Values.global.istio.ingress }} + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + {{- end }} + spec: + dnsPolicy: {{ .Values.dnsPolicy | quote }} + {{- with .Values.dnsConfig }} + dnsConfig: +{{ toYaml . | indent 8 }} + {{- end }} + + {{- with .Values.image.pullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ include "casa.name" . }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + env: + {{- include "casa.usr-envs" . | indent 12 }} + {{- include "casa.usr-secret-envs" . | indent 12 }} + {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} + command: + - /bin/sh + - -c + - | + /usr/bin/python3 /scripts/updatelbip.py & + /app/scripts/entrypoint.sh + {{- end }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: {{ .Values.service.name }} + containerPort: {{ .Values.service.port}} + protocol: TCP + envFrom: + - configMapRef: + name: {{ .Release.Name }}-config-cm + {{ if .Values.global.usrEnvs.secret }} + - secretRef: + name: {{ .Release.Name }}-global-user-custom-envs + {{- end }} + {{ if .Values.global.usrEnvs.normal }} + - configMapRef: + name: {{ .Release.Name }}-global-user-custom-envs + {{- end }} + volumeMounts: + {{- with .Values.volumeMounts }} +{{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.global.jackrabbit.enabled }} + - name: cn-jackrabbit-admin-pass + mountPath: /etc/gluu/conf/jackrabbit_admin_password + subPath: jackrabbit_admin_password + {{- end }} + {{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} + - mountPath: {{ .Values.global.cnGoogleApplicationCredentials }} + name: google-sa + subPath: google-credentials.json + {{- end }} + + {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} + - name: {{ include "casa.fullname" .}}-updatelbip + mountPath: "/scripts" + {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} + {{- if not .Values.global.istio.enabled }} + - name: cb-crt + mountPath: "/etc/certs/couchbase.crt" + subPath: couchbase.crt + {{- end }} + {{- end }} + livenessProbe: +{{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: +{{- toYaml .Values.readinessProbe | nindent 12 }} + {{- if or (eq .Values.global.storageClass.provisioner "microk8s.io/hostpath" ) (eq .Values.global.storageClass.provisioner "k8s.io/minikube-hostpath") }} + resources: {} + {{- else if .Values.global.cloud.testEnviroment }} + resources: {} + {{- else }} + resources: +{{- toYaml .Values.resources | nindent 12 }} + {{- end }} + volumes: + {{- with .Values.volumes }} +{{- toYaml . | nindent 8 }} + {{- end }} + {{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} + - name: google-sa + secret: + secretName: {{ .Release.Name }}-google-sa + {{- end }} + + {{- if .Values.global.jackrabbit.enabled }} + - name: cn-jackrabbit-admin-pass + secret: + secretName: cn-jackrabbit-admin-pass + {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} + + {{- if not .Values.global.istio.enabled }} + - name: cb-crt + secret: + secretName: {{ .Release.Name }}-cb-crt + {{- end }} + {{- end }} + {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} + - name: {{ include "casa.fullname" . }}-updatelbip + configMap: + name: {{ .Release.Name }}-updatelbip + {{- end }} + {{- if not .Values.global.isFqdnRegistered }} + hostAliases: + - ip: {{ .Values.global.lbIp }} + hostnames: + - {{ .Values.global.fqdn }} + {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/hpa.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/hpa.yaml new file mode 100644 index 00000000000..835909e4961 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/hpa.yaml @@ -0,0 +1,39 @@ +{{ if .Values.hpa.enabled -}} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: autoscaling/v1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "casa.fullname" . }} + labels: + APP_NAME: casa +{{ include "casa.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "casa.fullname" . }} + minReplicas: {{ .Values.hpa.minReplicas }} + maxReplicas: {{ .Values.hpa.maxReplicas }} + {{- if .Values.hpa.targetCPUUtilizationPercentage }} + targetCPUUtilizationPercentage: {{ .Values.hpa.targetCPUUtilizationPercentage }} + {{- else if .Values.hpa.metrics }} + metrics: + {{- with .Values.hpa.metrics }} +{{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} + {{- if .Values.hpa.behavior }} + behavior: + {{- with .Values.hpa.behavior }} +{{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/service.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/service.yaml new file mode 100644 index 00000000000..ab4cf59b102 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/service.yaml @@ -0,0 +1,32 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.global.casa.casaServiceName }} + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: casa +{{ include "casa.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + {{- if .Values.global.alb.ingress }} + type: NodePort + {{- end }} + ports: + - port: {{ .Values.service.port }} + name: {{ .Values.service.name }} + selector: + app: {{ .Release.Name }}-{{ include "casa.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- with .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: +{{ toYaml . | indent 4 }} + {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/user-custom-secret-envs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/user-custom-secret-envs.yaml new file mode 100644 index 00000000000..253106b46e9 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/templates/user-custom-secret-envs.yaml @@ -0,0 +1,23 @@ +{{ if .Values.usrEnvs.secret }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs + labels: + APP_NAME: casa +{{ include "casa.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/values.yaml new file mode 100644 index 00000000000..36139212676 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/casa/values.yaml @@ -0,0 +1,100 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +# -- Gluu Casa ("Casa") is a self-service web portal for end-users to manage authentication and authorization preferences for their account in a Gluu Server. +# -- Configure the HorizontalPodAutoscaler +hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} +# -- Add custom normal and secret envs to the service +usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} +# -- Add custom dns policy +dnsPolicy: "" +# -- Add custom dns config +dnsConfig: {} +image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: gluufederation/casa + # -- Image tag to use for deploying. + tag: 5.0.0_dev + # -- Image Pull Secrets + pullSecrets: [ ] +# -- Service replica number. +replicas: 1 +# -- Resource specs. +resources: + limits: + # -- CPU limit. + cpu: 500m + # -- Memory limit. + memory: 500Mi + requests: + # -- CPU request. + cpu: 500m + # -- Memory request. + memory: 500Mi +service: + # -- Port of the casa service. Please keep it as default. + port: 8080 + # -- The name of the casa port within the casa service. Please keep it as default. + name: http-casa + # -- Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP + sessionAffinity: None + # -- the maximum session sticky time if sessionAffinity is ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: 10800 +# -- Configure the liveness healthcheck for casa if needed. +livenessProbe: + httpGet: + # -- http liveness probe endpoint + path: /casa/health-check + port: http-casa + initialDelaySeconds: 25 + periodSeconds: 25 + timeoutSeconds: 5 +# -- Configure the readiness healthcheck for the casa if needed. +readinessProbe: + httpGet: + # -- http readiness probe endpoint + path: /casa/health-check + port: http-casa + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 +# -- Configure any additional volumes that need to be attached to the pod +volumes: [] +# -- Configure any additional volumesMounts that need to be attached to the containers +volumeMounts: [] + +nameOverride: "" +fullnameOverride: "" + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# -- Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken +additionalAnnotations: { } \ No newline at end of file diff --git a/charts/jans/charts/config-api/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/.helmignore similarity index 100% rename from charts/jans/charts/config-api/.helmignore rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/.helmignore diff --git a/charts/jans/charts/client-api/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/Chart.yaml similarity index 64% rename from charts/jans/charts/client-api/Chart.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/Chart.yaml index b038558676a..be6477ecb7c 100644 --- a/charts/jans/charts/client-api/Chart.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/Chart.yaml @@ -1,22 +1,22 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: v2 name: client-api -version: 1.0.0-b11 -kubeVersion: ">=v1.19.0-0" +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" description: Middleware API to help application developers call an OAuth, OpenID or UMA server. You may wonder why this is necessary. It makes it easier for client developers to use OpenID signing and encryption features, without becoming crypto experts. This API provides some high level endpoints to do some of the heavy lifting. type: application keywords: - client - API -home: https://jans.org/docs/oxd +home: https://gluu.org/docs/oxd sources: - https://github.com/JanssenProject/jans-client-api - https://github.com/JanssenProject/docker-jans-client-api - - https://github.com/JanssenFederation/cloud-native-edition/tree/master/pyjans/kubernetes/templates/helm/jans/charts/client-api + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/client-api maintainers: - name: Mohammad Abudayyeh - email: support@jans.io + email: support@gluu.org url: https://github.com/moabu -icon: https://jans.io/favicon.ico -appVersion: "1.0.0-b11" +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: "5.0.0" diff --git a/charts/jans/charts/client-api/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/README.md similarity index 64% rename from charts/jans/charts/client-api/README.md rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/README.md index dcdebfa7a64..7d8ebaf9493 100644 --- a/charts/jans/charts/client-api/README.md +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/README.md @@ -1,31 +1,33 @@ # client-api -![Version: 1.0.0-b11](https://img.shields.io/badge/Version-1.0.0--b11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-b11](https://img.shields.io/badge/AppVersion-1.0.0--b11-informational?style=flat-square) +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) Middleware API to help application developers call an OAuth, OpenID or UMA server. You may wonder why this is necessary. It makes it easier for client developers to use OpenID signing and encryption features, without becoming crypto experts. This API provides some high level endpoints to do some of the heavy lifting. -**Homepage:** +**Homepage:** ## Maintainers | Name | Email | Url | | ---- | ------ | --- | -| Mohammad Abudayyeh | support@jans.io | https://github.com/moabu | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | ## Source Code * * -* +* ## Requirements -Kubernetes: `>=v1.19.0-0` +Kubernetes: `>=v1.21.0-0` ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken | +| additionalLabels | object | `{}` | Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} | | affinity | object | `{}` | | | dnsConfig | object | `{}` | Add custom dns config | | dnsPolicy | string | `""` | Add custom dns policy | @@ -38,17 +40,19 @@ Kubernetes: `>=v1.19.0-0` | image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | | image.pullSecrets | list | `[]` | Image Pull Secrets | | image.repository | string | `"janssenproject/client-api"` | Image to use for deploying. | -| image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | +| image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | | livenessProbe | object | `{"exec":{"command":["curl","-k","https://localhost:8443/health-check"]},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for the auth server if needed. | | livenessProbe.exec | object | `{"command":["curl","-k","https://localhost:8443/health-check"]}` | Executes the python3 healthcheck. | | nodeSelector | object | `{}` | | -| readinessProbe | object | `{"exec":{"command":["curl","-k","https://localhost:8443/health-check"]},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the auth server if needed. | +| readinessProbe | object | `{"initialDelaySeconds":60,"periodSeconds":25,"tcpSocket":{"port":8443},"timeoutSeconds":5}` | Configure the readiness healthcheck for the auth server if needed. | | replicas | int | `1` | Service replica number. | | resources | object | `{"limits":{"cpu":"1000m","memory":"400Mi"},"requests":{"cpu":"1000m","memory":"400Mi"}}` | Resource specs. | | resources.limits.cpu | string | `"1000m"` | CPU limit. | | resources.limits.memory | string | `"400Mi"` | Memory limit. | | resources.requests.cpu | string | `"1000m"` | CPU request. | | resources.requests.memory | string | `"400Mi"` | Memory request. | +| service.sessionAffinity | string | `"None"` | Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP | +| service.sessionAffinityConfig | object | `{"clientIP":{"timeoutSeconds":10800}}` | the maximum session sticky time if sessionAffinity is ClientIP | | tolerations | list | `[]` | | | usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | | usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | diff --git a/charts/jans/charts/client-api/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/_helpers.tpl similarity index 100% rename from charts/jans/charts/client-api/templates/_helpers.tpl rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/_helpers.tpl diff --git a/charts/jans/charts/client-api/templates/client-api-destination-rules.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/client-api-destination-rules.yaml similarity index 53% rename from charts/jans/charts/client-api/templates/client-api-destination-rules.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/client-api-destination-rules.yaml index 9bebcc09bea..22f580790ea 100644 --- a/charts/jans/charts/client-api/templates/client-api-destination-rules.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/client-api-destination-rules.yaml @@ -1,11 +1,21 @@ {{- if .Values.global.istio.enabled }} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: {{ .Release.Name }}-client-api-mtls namespace: {{.Release.Namespace}} + labels: + APP_NAME: client-api +{{ include "client-api.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: host: {{ index .Values "global" "client-api" "clientApiServerServiceName" }}.{{ .Release.Namespace }}.svc.cluster.local trafficPolicy: diff --git a/charts/jans/charts/client-api/templates/deployment.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/deployment.yaml similarity index 87% rename from charts/jans/charts/client-api/templates/deployment.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/deployment.yaml index 92e24fcb5de..7b28bda8bf1 100644 --- a/charts/jans/charts/client-api/templates/deployment.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/deployment.yaml @@ -1,4 +1,4 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: apps/v1 kind: Deployment @@ -8,6 +8,13 @@ metadata: labels: APP_NAME: client-api {{ include "client-api.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: replicas: {{ .Values.replicas }} selector: @@ -79,15 +86,8 @@ spec: name: google-sa subPath: google-credentials.json {{- end }} - {{- if eq .Values.global.cnPersistenceType "sql" }} - - name: sql-pass - mountPath: "/etc/jans/conf/sql_password" - subPath: sql_password - {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} - - name: cb-pass - mountPath: "/etc/jans/conf/couchbase_password" - subPath: couchbase_password {{- if not .Values.global.istio.enabled }} - name: cb-crt mountPath: "/etc/certs/couchbase.crt" @@ -115,15 +115,9 @@ spec: secret: secretName: {{ .Release.Name }}-google-sa {{- end }} - {{- if eq .Values.global.cnPersistenceType "sql" }} - - name: sql-pass - secret: - secretName: {{ .Release.Name }}-sql-pass - {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} - - name: cb-pass - secret: - secretName: {{ .Release.Name }}-cb-pass + {{- if not .Values.global.istio.enabled }} - name: cb-crt secret: @@ -135,7 +129,7 @@ spec: configMap: name: {{ .Release.Name }}-updatelbip {{- end }} - {{- if not .Values.global.isFqdnRegistered }} + {{- if not .Values.global.isFqdnRegistered }} hostAliases: - ip: {{ .Values.global.lbIp }} hostnames: diff --git a/charts/jans/charts/client-api/templates/hpa.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/hpa.yaml similarity index 68% rename from charts/jans/charts/client-api/templates/hpa.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/hpa.yaml index 453a914dde0..2409795f233 100644 --- a/charts/jans/charts/client-api/templates/hpa.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/hpa.yaml @@ -1,10 +1,20 @@ {{ if .Values.hpa.enabled -}} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: {{ include "client-api.fullname" . }} + labels: + APP_NAME: client-api +{{ include "client-api.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: scaleTargetRef: apiVersion: apps/v1 diff --git a/charts/jans/charts/client-api/templates/networkpolicy.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/networkpolicy.yaml similarity index 67% rename from charts/jans/charts/client-api/templates/networkpolicy.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/networkpolicy.yaml index 263aed447f9..fa3093109e8 100644 --- a/charts/jans/charts/client-api/templates/networkpolicy.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/networkpolicy.yaml @@ -3,6 +3,16 @@ apiVersion: networking.k8s.io/v1 metadata: namespace: {{ .Release.Namespace }} name: client-api-policy + labels: + APP_NAME: client-api +{{ include "client-api.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: policyTypes: - Ingress diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/service.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/service.yaml new file mode 100644 index 00000000000..286f7c751d1 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/service.yaml @@ -0,0 +1,31 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Service +metadata: + # the name must match the application + name: {{ index .Values "global" "client-api" "clientApiServerServiceName" }} + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: client-api +{{ include "client-api.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + ports: + - port: 8444 + name: tcp-{{ include "client-api.name" . }}-admin-gui + - port: 8443 + name: tcp-{{ include "client-api.name" . }}-app-connector + selector: + app: {{ .Release.Name }}-{{ include "client-api.name" . }} + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- with .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: +{{ toYaml . | indent 4 }} + {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/user-custom-secret-envs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/user-custom-secret-envs.yaml new file mode 100644 index 00000000000..fe356f484af --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/templates/user-custom-secret-envs.yaml @@ -0,0 +1,23 @@ +{{ if .Values.usrEnvs.secret }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs + labels: + APP_NAME: client-api +{{ include "client-api.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} \ No newline at end of file diff --git a/charts/jans/charts/client-api/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/values.yaml similarity index 70% rename from charts/jans/charts/client-api/values.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/values.yaml index bb13128ab96..235a14929b8 100644 --- a/charts/jans/charts/client-api/values.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/client-api/values.yaml @@ -1,4 +1,4 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 # -- Middleware API to help application developers call an OAuth, OpenID or UMA server. You may wonder why this is necessary. It makes it easier for client developers to use OpenID signing and encryption features, without becoming crypto experts. This API provides some high level endpoints to do some of the heavy lifting. # -- Configure the HorizontalPodAutoscaler @@ -29,7 +29,7 @@ image: # -- Image to use for deploying. repository: janssenproject/client-api # -- Image tag to use for deploying. - tag: 1.0.0_b11 + tag: 1.0.0-beta.14 # -- Image Pull Secrets pullSecrets: [ ] # -- Service replica number. @@ -46,6 +46,13 @@ resources: cpu: 1000m # -- Memory request. memory: 400Mi +service: + # -- Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP + sessionAffinity: None + # -- the maximum session sticky time if sessionAffinity is ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: 10800 # -- Configure the liveness healthcheck for the auth server if needed. livenessProbe: # -- Executes the python3 healthcheck. @@ -59,14 +66,11 @@ livenessProbe: timeoutSeconds: 5 # -- Configure the readiness healthcheck for the auth server if needed. readinessProbe: - exec: - command: - - curl - - -k - - https://localhost:8443/health-check - initialDelaySeconds: 25 - periodSeconds: 25 + tcpSocket: + port: 8443 + initialDelaySeconds: 60 timeoutSeconds: 5 + periodSeconds: 25 # -- Configure any additional volumes that need to be attached to the pod volumes: [] # -- Configure any additional volumesMounts that need to be attached to the containers @@ -77,3 +81,8 @@ nodeSelector: {} tolerations: [] affinity: {} + +# -- Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken +additionalAnnotations: { } diff --git a/charts/jans/charts/persistence/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/.helmignore similarity index 100% rename from charts/jans/charts/persistence/.helmignore rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/.helmignore diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/Chart.yaml new file mode 100644 index 00000000000..5126ccde6ed --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/Chart.yaml @@ -0,0 +1,21 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v2 +name: cn-istio-ingress +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" +description: Istio Gateway +type: application +keywords: + - istio + - gateway +home: https://gluu.org/docs/gluu-server/ +sources: + - https://gluu.org/docs/gluu-server/ + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress +maintainers: + - name: Mohammad Abudayyeh + email: support@gluu.org + url: https://github.com/moabu +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: "5.0.0" diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/README.md new file mode 100644 index 00000000000..a75a6c9ea3b --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/README.md @@ -0,0 +1,25 @@ +# cn-istio-ingress + +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) + +Istio Gateway + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | + +## Source Code + +* +* + +## Requirements + +Kubernetes: `>=v1.21.0-0` + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0) diff --git a/charts/jans/charts/cn-istio-ingress/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/templates/_helpers.tpl similarity index 100% rename from charts/jans/charts/cn-istio-ingress/templates/_helpers.tpl rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/templates/_helpers.tpl diff --git a/charts/jans/charts/cn-istio-ingress/templates/gateway.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/templates/gateway.yaml similarity index 68% rename from charts/jans/charts/cn-istio-ingress/templates/gateway.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/templates/gateway.yaml index 2e206e4eb6e..e6013652c82 100644 --- a/charts/jans/charts/cn-istio-ingress/templates/gateway.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/templates/gateway.yaml @@ -3,6 +3,14 @@ kind: Gateway metadata: name: {{ .Release.Name }}-global-gtw namespace: {{ .Release.Namespace }} +{{- if .Values.global.istio.additionalLabels }} + labels: +{{ toYaml .Values.global.istio.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.global.istio.additionalAnnotations }} + annotations: +{{ toYaml .Values.global.istio.additionalAnnotations | indent 4 }} +{{- end }} spec: selector: istio: ingressgateway diff --git a/charts/jans/charts/cn-istio-ingress/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/values.yaml similarity index 100% rename from charts/jans/charts/cn-istio-ingress/values.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/cn-istio-ingress/values.yaml diff --git a/charts/jans/charts/fido2/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/.helmignore similarity index 100% rename from charts/jans/charts/fido2/.helmignore rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/.helmignore diff --git a/charts/jans/charts/config-api/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/Chart.yaml similarity index 57% rename from charts/jans/charts/config-api/Chart.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/Chart.yaml index e506af2a87a..c9255f40218 100644 --- a/charts/jans/charts/config-api/Chart.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/Chart.yaml @@ -1,22 +1,22 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: v2 name: config-api -version: 1.0.0-b11 -kubeVersion: ">=v1.19.0-0" +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" description: Jans Config Api endpoints can be used to configure jans-auth-server, which is an open-source OpenID Connect Provider (OP) and UMA Authorization Server (AS) type: application keywords: - configuration - API -home: https://jans.io +home: https://gluu.org/docs/gluu-server sources: - https://github.com/JanssenProject/jans-config-api - https://github.com/JanssenProject/docker-jans-config-api - - https://github.com/JanssenFederation/cloud-native-edition/tree/master/pyjans/kubernetes/templates/helm/jans/charts/config-api + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/config-api maintainers: - name: Mohammad Abudayyeh - email: support@jans.io + email: support@gluu.org url: https://github.com/moabu -icon: https://jans.io/favicon.ico -appVersion: "1.0.0-b11" +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: "5.0.0" diff --git a/charts/jans/charts/config-api/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/README.md similarity index 65% rename from charts/jans/charts/config-api/README.md rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/README.md index 2854fbd521c..fe557b9d75a 100644 --- a/charts/jans/charts/config-api/README.md +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/README.md @@ -1,31 +1,33 @@ # config-api -![Version: 1.0.0-b11](https://img.shields.io/badge/Version-1.0.0--b11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-b11](https://img.shields.io/badge/AppVersion-1.0.0--b11-informational?style=flat-square) +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) Jans Config Api endpoints can be used to configure jans-auth-server, which is an open-source OpenID Connect Provider (OP) and UMA Authorization Server (AS) -**Homepage:** +**Homepage:** ## Maintainers | Name | Email | Url | | ---- | ------ | --- | -| Mohammad Abudayyeh | support@jans.io | https://github.com/moabu | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | ## Source Code * * -* +* ## Requirements -Kubernetes: `>=v1.19.0-0` +Kubernetes: `>=v1.21.0-0` ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken | +| additionalLabels | object | `{}` | Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} | | affinity | object | `{}` | | | dnsConfig | object | `{}` | Add custom dns config | | dnsPolicy | string | `""` | Add custom dns policy | @@ -39,12 +41,12 @@ Kubernetes: `>=v1.19.0-0` | image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | | image.pullSecrets | list | `[]` | Image Pull Secrets | | image.repository | string | `"janssenproject/config-api"` | Image to use for deploying. | -| image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | +| image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | | livenessProbe | object | `{"httpGet":{"path":"/jans-config-api/api/v1/health/live","port":8074},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for the auth server if needed. | -| livenessProbe.httpGet | object | `{"path":"/jans-config-api/api/v1/health/live","port":8074}` | Executes the python3 healthcheck. https://github.com/JanssenFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py | +| livenessProbe.httpGet | object | `{"path":"/jans-config-api/api/v1/health/live","port":8074}` | Executes the python3 healthcheck. https://github.com/GluuFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py | | nameOverride | string | `""` | | | nodeSelector | object | `{}` | | -| readinessProbe | object | `{"httpGet":{"path":"/jans-config-api/api/v1/health/ready","port":8074},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the auth server if needed. https://github.com/JanssenFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py | +| readinessProbe | object | `{"httpGet":{"path":"/jans-config-api/api/v1/health/ready","port":8074},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the auth server if needed. https://github.com/GluuFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py | | replicas | int | `1` | Service replica number. | | resources | object | `{"limits":{"cpu":"2500m","memory":"2500Mi"},"requests":{"cpu":"2500m","memory":"2500Mi"}}` | Resource specs. | | resources.limits.cpu | string | `"2500m"` | CPU limit. | @@ -52,6 +54,8 @@ Kubernetes: `>=v1.19.0-0` | resources.requests.cpu | string | `"2500m"` | CPU request. | | resources.requests.memory | string | `"2500Mi"` | Memory request. | | service.name | string | `"http-config-api"` | The name of the config-api port within the config-api service. Please keep it as default. | +| service.sessionAffinity | string | `"None"` | Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP | +| service.sessionAffinityConfig | object | `{"clientIP":{"timeoutSeconds":10800}}` | the maximum session sticky time if sessionAffinity is ClientIP | | tolerations | list | `[]` | | | usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | | usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | diff --git a/charts/jans/charts/config-api/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/_helpers.tpl similarity index 100% rename from charts/jans/charts/config-api/templates/_helpers.tpl rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/_helpers.tpl diff --git a/charts/jans/charts/config-api/templates/config-api-destination-rules.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/config-api-destination-rules.yaml similarity index 53% rename from charts/jans/charts/config-api/templates/config-api-destination-rules.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/config-api-destination-rules.yaml index 8a3ce219a9a..78a019dd48a 100644 --- a/charts/jans/charts/config-api/templates/config-api-destination-rules.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/config-api-destination-rules.yaml @@ -1,11 +1,21 @@ {{- if .Values.global.istio.enabled }} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: {{ .Release.Name }}-config-api-mtls namespace: {{.Release.Namespace}} + labels: + APP_NAME: config-api +{{ include "config-api.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: host: {{ index .Values "global" "config-api" "configApiServerServiceName" }}.{{ .Release.Namespace }}.svc.cluster.local trafficPolicy: diff --git a/charts/jans/charts/config-api/templates/deployment.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/deployment.yaml similarity index 69% rename from charts/jans/charts/config-api/templates/deployment.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/deployment.yaml index 1d992b23cc8..5301f551cc4 100644 --- a/charts/jans/charts/config-api/templates/deployment.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/deployment.yaml @@ -1,4 +1,4 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: apps/v1 kind: Deployment @@ -8,6 +8,13 @@ metadata: labels: APP_NAME: config-api {{ include "config-api.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: replicas: {{ .Values.replicas }} selector: @@ -61,20 +68,27 @@ spec: {{- with .Values.volumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} + {{- if index .Values "global" "admin-ui" "enabled" }} + - mountPath: {{ index .Values "global" "admin-ui" "adminUiApiKeyFile" }} + name: admin-ui-license-api-key + subPath: admin_ui_api_key + - mountPath: {{ index .Values "global" "admin-ui" "adminUiProductCodeFile" }} + name: admin-ui-license-product-code + subPath: admin_ui_product_code + - mountPath: {{ index .Values "global" "admin-ui" "adminUiSharedKeyFile" }} + name: admin-ui-license-shared-key + subPath: admin_ui_shared_key + - mountPath: {{ index .Values "global" "admin-ui" "adminUiManagementKeyFile" }} + name: admin-ui-license-management-key + subPath: admin_ui_management_key + {{- end }} {{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} - mountPath: {{ .Values.global.cnGoogleApplicationCredentials }} name: google-sa subPath: google-credentials.json {{- end }} - {{- if eq .Values.global.cnPersistenceType "sql" }} - - name: sql-pass - mountPath: "/etc/jans/conf/sql_password" - subPath: sql_password - {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} - - name: cb-pass - mountPath: "/etc/jans/conf/couchbase_password" - subPath: couchbase_password {{- if not .Values.global.istio.enabled }} - name: cb-crt mountPath: "/etc/certs/couchbase.crt" @@ -97,20 +111,40 @@ spec: {{- with .Values.volumes }} {{- toYaml . | nindent 8 }} {{- end }} + {{- if index .Values "global" "admin-ui" "enabled" }} + - name: admin-ui-license-api-key + secret: + secretName: {{ .Release.Name }}-admin-ui-license + items: + - key: admin_ui_api_key + path: admin_ui_api_key + - name: admin-ui-license-product-code + secret: + secretName: {{ .Release.Name }}-admin-ui-license + items: + - key: admin_ui_product_code + path: admin_ui_product_code + - name: admin-ui-license-shared-key + secret: + secretName: {{ .Release.Name }}-admin-ui-license + items: + - key: admin_ui_shared_key + path: admin_ui_shared_key + - name: admin-ui-license-management-key + secret: + secretName: {{ .Release.Name }}-admin-ui-license + items: + - key: admin_ui_management_key + path: admin_ui_management_key + {{- end }} {{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} - name: google-sa secret: secretName: {{ .Release.Name }}-google-sa {{- end }} - {{- if eq .Values.global.cnPersistenceType "sql" }} - - name: sql-pass - secret: - secretName: {{ .Release.Name }}-sql-pass - {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} - - name: cb-pass - secret: - secretName: {{ .Release.Name }}-cb-pass + {{- if not .Values.global.istio.enabled }} - name: cb-crt secret: diff --git a/charts/jans/charts/config-api/templates/hpa.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/hpa.yaml similarity index 68% rename from charts/jans/charts/config-api/templates/hpa.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/hpa.yaml index 004cd90e415..8807ac220ac 100644 --- a/charts/jans/charts/config-api/templates/hpa.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/hpa.yaml @@ -1,10 +1,20 @@ {{ if .Values.hpa.enabled -}} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: {{ include "config-api.fullname" . }} + labels: + APP_NAME: config-api +{{ include "config-api.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: scaleTargetRef: apiVersion: apps/v1 diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/service.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/service.yaml new file mode 100644 index 00000000000..527971c98d3 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/templates/service.yaml @@ -0,0 +1,31 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Service +metadata: + # the name must match the application + name: {{ index .Values "global" "config-api" "configApiServerServiceName" }} + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: config-api +{{ include "config-api.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + ports: + - port: 9444 + name: tcp-{{ include "config-api.name" . }}-ssl + - port: 8074 + name: tcp-{{ include "config-api.name" . }}-http + selector: + app: {{ .Release.Name }}-{{ include "config-api.name" . }} + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- with .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: +{{ toYaml . | indent 4 }} + {{- end }} diff --git a/charts/jans/charts/config-api/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/values.yaml similarity index 65% rename from charts/jans/charts/config-api/values.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/values.yaml index ce39383e4f4..aed84071dc0 100644 --- a/charts/jans/charts/config-api/values.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config-api/values.yaml @@ -1,6 +1,6 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 -# -- Janssen Admin UI. This shouldn't be internet facing. +# -- Gluu Admin UI. This shouldn't be internet facing. # -- Configure the HorizontalPodAutoscaler hpa: enabled: true @@ -34,7 +34,7 @@ image: # -- Image to use for deploying. repository: janssenproject/config-api # -- Image tag to use for deploying. - tag: 1.0.0_b11 + tag: 1.0.0-beta.14 # -- Image Pull Secrets pullSecrets: [ ] # -- Service replica number. @@ -54,10 +54,16 @@ resources: service: # -- The name of the config-api port within the config-api service. Please keep it as default. name: http-config-api + # -- Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP + sessionAffinity: None + # -- the maximum session sticky time if sessionAffinity is ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: 10800 # -- Configure the liveness healthcheck for the auth server if needed. livenessProbe: # -- Executes the python3 healthcheck. - # https://github.com/JanssenFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py + # https://github.com/GluuFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py httpGet: path: /jans-config-api/api/v1/health/live port: 8074 @@ -65,7 +71,7 @@ livenessProbe: periodSeconds: 30 timeoutSeconds: 5 # -- Configure the readiness healthcheck for the auth server if needed. -# https://github.com/JanssenFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py +# https://github.com/GluuFederation/docker-oxauth/blob/4.3/scripts/healthcheck.py readinessProbe: httpGet: path: /jans-config-api/api/v1/health/ready @@ -83,4 +89,9 @@ affinity: {} # -- Configure any additional volumes that need to be attached to the pod volumes: [] # -- Configure any additional volumesMounts that need to be attached to the containers -volumeMounts: [] \ No newline at end of file +volumeMounts: [] + +# -- Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken +additionalAnnotations: { } diff --git a/charts/jans/charts/config/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/.helmignore similarity index 100% rename from charts/jans/charts/config/.helmignore rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config/.helmignore diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/Chart.yaml new file mode 100644 index 00000000000..38fb62ac79a --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/Chart.yaml @@ -0,0 +1,22 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v2 +name: config +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" +description: Configuration parameters for setup and initial configuration secret and config layers used by Gluu services. +type: application +keywords: + - configuration + - secrets +home: https://gluu.org/docs/gluu-server/reference/container-configs/ +sources: + - https://gluu.org/docs/gluu-server/reference/container-configs/ + - https://github.com/JanssenProject/docker-jans-configurator + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/config +maintainers: + - name: Mohammad Abudayyeh + email: support@gluu.org + url: https://github.com/moabu +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: "5.0.0" diff --git a/charts/jans/charts/config/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/README.md similarity index 65% rename from charts/jans/charts/config/README.md rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config/README.md index e2679de8856..40b52512e08 100644 --- a/charts/jans/charts/config/README.md +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/README.md @@ -1,68 +1,83 @@ # config -![Version: 1.0.0-b11](https://img.shields.io/badge/Version-1.0.0--b11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-b11](https://img.shields.io/badge/AppVersion-1.0.0--b11-informational?style=flat-square) +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) -Configuration parameters for setup and initial configuration secret and config layers used by Janssen services. +Configuration parameters for setup and initial configuration secret and config layers used by Gluu services. -**Homepage:** +**Homepage:** ## Maintainers | Name | Email | Url | | ---- | ------ | --- | -| Mohammad Abudayyeh | support@jans.io | https://github.com/moabu | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | ## Source Code -* -* -* +* +* +* ## Requirements -Kubernetes: `>=v1.19.0-0` +Kubernetes: `>=v1.21.0-0` ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken | +| additionalLabels | object | `{}` | Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} | | adminPassword | string | `"Test1234#"` | Admin password to log in to the UI. | | city | string | `"Austin"` | City. Used for certificate creation. | +| cnOxtrustConfigGeneration | bool | `true` | | | configmap.cnCacheType | string | `"NATIVE_PERSISTENCE"` | Cache type. `NATIVE_PERSISTENCE`, `REDIS`. or `IN_MEMORY`. Defaults to `NATIVE_PERSISTENCE` . | +| configmap.cnCasaEnabled | bool | `false` | Enable Casa flag . | | configmap.cnClientApiAdminCertCn | string | `"client-api"` | Client-api OAuth client admin certificate common name. This should be left to the default value client-api . | | configmap.cnClientApiApplicationCertCn | string | `"client-api"` | Client-api OAuth client application certificate common name. This should be left to the default value client-api. | | configmap.cnClientApiBindIpAddresses | string | `"*"` | Client-api bind address. This limits what ip ranges can access the client-api. This should be left as * and controlled by a NetworkPolicy | -| configmap.cnConfigGoogleSecretNamePrefix | string | `"jans"` | Prefix for Janssen configuration secret in Google Secret Manager. Defaults to jans. If left intact jans-configuration secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google. | +| configmap.cnConfigGoogleSecretNamePrefix | string | `"gluu"` | Prefix for Gluu configuration secret in Google Secret Manager. Defaults to gluu. If left intact gluu-configuration secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google. | | configmap.cnConfigGoogleSecretVersionId | string | `"latest"` | Secret version to be used for configuration. Defaults to latest and should normally always stay that way. Used only when global.configAdapterName and global.configSecretAdapter is set to google. Used only when global.configAdapterName and global.configSecretAdapter is set to google. | | configmap.cnConfigKubernetesConfigMap | string | `"cn"` | The name of the Kubernetes ConfigMap that will hold the configuration layer | -| configmap.cnCouchbaseBucketPrefix | string | `"jans"` | The prefix of couchbase buckets. This helps with separation in between different environments and allows for the same couchbase cluster to be used by different setups of Janssen. | +| configmap.cnCouchbaseBucketPrefix | string | `"jans"` | The prefix of couchbase buckets. This helps with separation in between different environments and allows for the same couchbase cluster to be used by different setups of Gluu. | | configmap.cnCouchbaseCertFile | string | `"/etc/certs/couchbase.crt"` | Location of `couchbase.crt` used by Couchbase SDK for tls termination. The file path must end with couchbase.crt. In mTLS setups this is not required. | | configmap.cnCouchbaseCrt | string | `"SWFtTm90YVNlcnZpY2VBY2NvdW50Q2hhbmdlTWV0b09uZQo="` | Couchbase certificate authority string. This must be encoded using base64. This can also be found in your couchbase UI Security > Root Certificate. In mTLS setups this is not required. | | configmap.cnCouchbaseIndexNumReplica | int | `0` | The number of replicas per index created. Please note that the number of index nodes must be one greater than the number of index replicas. That means if your couchbase cluster only has 2 index nodes you cannot place the number of replicas to be higher than 1. | | configmap.cnCouchbasePassword | string | `"P@ssw0rd"` | Couchbase password for the restricted user config.configmap.cnCouchbaseUser that is often used inside the services. The password must contain one digit, one uppercase letter, one lower case letter and one symbol . | -| configmap.cnCouchbasePasswordFile | string | `"/etc/jans/conf/couchbase_password"` | The location of the Couchbase restricted user config.configmap.cnCouchbaseUser password. The file path must end with couchbase_password | +| configmap.cnCouchbasePasswordFile | string | `"/etc/gluu/conf/couchbase_password"` | The location of the Couchbase restricted user config.configmap.cnCouchbaseUser password. The file path must end with couchbase_password | | configmap.cnCouchbaseSuperUser | string | `"admin"` | The Couchbase super user (admin) user name. This user is used during initialization only. | | configmap.cnCouchbaseSuperUserPassword | string | `"Test1234#"` | Couchbase password for the super user config.configmap.cnCouchbaseSuperUser that is used during the initialization process. The password must contain one digit, one uppercase letter, one lower case letter and one symbol | -| configmap.cnCouchbaseSuperUserPasswordFile | string | `"/etc/jans/conf/couchbase_superuser_password"` | The location of the Couchbase restricted user config.configmap.cnCouchbaseSuperUser password. The file path must end with couchbase_superuser_password. | -| configmap.cnCouchbaseUrl | string | `"cbjans.default.svc.cluster.local"` | Couchbase URL. Used only when global.cnPersistenceType is hybrid or couchbase. This should be in FQDN format for either remote or local Couchbase clusters. The address can be an internal address inside the kubernetes cluster | -| configmap.cnCouchbaseUser | string | `"jans"` | Couchbase restricted user. Used only when global.cnPersistenceType is hybrid or couchbase. | -| configmap.cnDocumentStoreType | string | `"LOCAL"` | Document store type to use for shibboleth files JCA or LOCAL. Note that if JCA is selected Apache Jackrabbit will be used. Jackrabbit also enables loading custom files across all services easily. | +| configmap.cnCouchbaseSuperUserPasswordFile | string | `"/etc/gluu/conf/couchbase_superuser_password"` | The location of the Couchbase restricted user config.configmap.cnCouchbaseSuperUser password. The file path must end with couchbase_superuser_password. | +| configmap.cnCouchbaseUrl | string | `"cbgluu.default.svc.cluster.local"` | Couchbase URL. Used only when global.cnPersistenceType is hybrid or couchbase. This should be in FQDN format for either remote or local Couchbase clusters. The address can be an internal address inside the kubernetes cluster | +| configmap.cnCouchbaseUser | string | `"gluu"` | Couchbase restricted user. Used only when global.cnPersistenceType is hybrid or couchbase. | +| configmap.cnDocumentStoreType | string | `"JCA"` | Document store type to use for shibboleth files JCA or LOCAL. Note that if JCA is selected Apache Jackrabbit will be used. Jackrabbit also enables loading custom files across all services easily. | | configmap.cnGoogleProjectId | string | `"google-project-to-save-config-and-secrets-to"` | Project id of the google project the secret manager belongs to. Used only when global.configAdapterName and global.configSecretAdapter is set to google. | -| configmap.cnGoogleSecretManagerPassPhrase | string | `"Test1234#"` | Passphrase for Janssen secret in Google Secret Manager. This is used for encrypting and decrypting data from the Google Secret Manager. Used only when global.configAdapterName and global.configSecretAdapter is set to google. | +| configmap.cnGoogleSecretManagerPassPhrase | string | `"Test1234#"` | Passphrase for Gluu secret in Google Secret Manager. This is used for encrypting and decrypting data from the Google Secret Manager. Used only when global.configAdapterName and global.configSecretAdapter is set to google. | | configmap.cnGoogleSecretManagerServiceAccount | string | `"SWFtTm90YVNlcnZpY2VBY2NvdW50Q2hhbmdlTWV0b09uZQo="` | | | configmap.cnGoogleSpannerDatabaseId | string | `""` | Google Spanner Database ID. Used only when global.cnPersistenceType is spanner. | | configmap.cnGoogleSpannerInstanceId | string | `""` | | +| configmap.cnJackrabbitAdminId | string | `"admin"` | Jackrabbit admin uid. | +| configmap.cnJackrabbitAdminIdFile | string | `"/etc/gluu/conf/jackrabbit_admin_id"` | The location of the Jackrabbit admin uid config.cnJackrabbitAdminId. The file path must end with jackrabbit_admin_id. | +| configmap.cnJackrabbitAdminPasswordFile | string | `"/etc/gluu/conf/jackrabbit_admin_password"` | The location of the Jackrabbit admin password jackrabbit.secrets.cnJackrabbitAdminPassword. The file path must end with jackrabbit_admin_password. | +| configmap.cnJackrabbitPostgresDatabaseName | string | `"jackrabbit"` | Jackrabbit postgres database name. | +| configmap.cnJackrabbitPostgresHost | string | `"postgresql.postgres.svc.cluster.local"` | Postgres url | +| configmap.cnJackrabbitPostgresPasswordFile | string | `"/etc/gluu/conf/postgres_password"` | The location of the Jackrabbit postgres password file jackrabbit.secrets.cnJackrabbitPostgresPassword. The file path must end with postgres_password. | +| configmap.cnJackrabbitPostgresPort | int | `5432` | Jackrabbit Postgres port | +| configmap.cnJackrabbitPostgresUser | string | `"jackrabbit"` | Jackrabbit Postgres uid | +| configmap.cnJackrabbitSyncInterval | int | `300` | Interval between files sync (default to 300 seconds). | +| configmap.cnJackrabbitUrl | string | `"http://jackrabbit:8080"` | Jackrabbit internal url. Normally left as default. | | configmap.cnJettyRequestHeaderSize | int | `8192` | Jetty header size in bytes in the auth server | | configmap.cnLdapUrl | string | `"opendj:1636"` | | | configmap.cnMaxRamPercent | string | `"75.0"` | Value passed to Java option -XX:MaxRAMPercentage | -| configmap.cnPersistenceLdapMapping | string | `"default"` | Boolean flag to enable/disable passport chart -- Specify data that should be saved in LDAP (one of default, user, cache, site, token, or session; default to default). Note this environment only takes effect when `global.cnPersistenceType` is set to `hybrid`. | +| configmap.cnPassportEnabled | bool | `false` | Boolean flag to enable/disable passport chart | +| configmap.cnPersistenceLdapMapping | string | `"default"` | Specify data that should be saved in LDAP (one of default, user, cache, site, token, or session; default to default). Note this environment only takes effect when `global.cnPersistenceType` is set to `hybrid`. | | configmap.cnRedisSentinelGroup | string | `""` | Redis Sentinel Group. Often set when `config.configmap.cnRedisType` is set to `SENTINEL`. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | | configmap.cnRedisSslTruststore | string | `""` | Redis SSL truststore. Optional. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | | configmap.cnRedisType | string | `"STANDALONE"` | Redis service type. `STANDALONE` or `CLUSTER`. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | | configmap.cnRedisUrl | string | `"redis.redis.svc.cluster.local:6379"` | Redis URL and port number :. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | | configmap.cnRedisUseSsl | bool | `false` | Boolean to use SSL in Redis. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | | configmap.cnSamlEnabled | bool | `false` | Enable SAML-related features; UI menu, etc. | -| configmap.cnSecretGoogleSecretNamePrefix | string | `"jans"` | Prefix for Janssen secret in Google Secret Manager. Defaults to jans. If left jans-secret secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google. | +| configmap.cnSecretGoogleSecretNamePrefix | string | `"gluu"` | Prefix for Gluu secret in Google Secret Manager. Defaults to gluu. If left gluu-secret secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google. | | configmap.cnSecretGoogleSecretVersionId | string | `"latest"` | | | configmap.cnSecretKubernetesSecret | string | `"cn"` | Kubernetes secret name holding configuration keys. Used when global.configSecretAdapter is set to kubernetes which is the default. | | configmap.cnSqlDbDialect | string | `"mysql"` | SQL database dialect. `mysql` or `pgsql` | @@ -78,18 +93,18 @@ Kubernetes: `>=v1.19.0-0` | countryCode | string | `"US"` | Country code. Used for certificate creation. | | dnsConfig | object | `{}` | Add custom dns config | | dnsPolicy | string | `""` | Add custom dns policy | -| email | string | `"support@jans.io"` | Email address of the administrator usually. Used for certificate creation. | +| email | string | `"support@gluu.org"` | Email address of the administrator usually. Used for certificate creation. | | fullNameOverride | string | `""` | | | image.pullSecrets | list | `[]` | Image Pull Secrets | -| image.repository | string | `"janssenproject/configuration-manager"` | Image to use for deploying. | -| image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | +| image.repository | string | `"janssenproject/configurator"` | Image to use for deploying. | +| image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | | ldapPassword | string | `"P@ssw0rds"` | LDAP admin password if OpennDJ is used for persistence. | | migration | object | `{"enabled":false,"migrationDataFormat":"ldif","migrationDir":"/ce-migration"}` | CE to CN Migration section | | migration.enabled | bool | `false` | Boolean flag to enable migration from CE | | migration.migrationDataFormat | string | `"ldif"` | migration data-format depending on persistence backend. Supported data formats are ldif, couchbase+json, spanner+avro, postgresql+json, and mysql+json. | | migration.migrationDir | string | `"/ce-migration"` | Directory holding all migration files | | nameOverride | string | `""` | | -| orgName | string | `"Janssen"` | Organization name. Used for certificate creation. | +| orgName | string | `"Gluu"` | Organization name. Used for certificate creation. | | redisPassword | string | `"P@assw0rd"` | Redis admin password if `config.configmap.cnCacheType` is set to `REDIS`. | | resources | object | `{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}}` | Resource specs. | | resources.limits.cpu | string | `"300m"` | CPU limit. | diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/_helpers.tpl new file mode 100644 index 00000000000..3d589814438 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/_helpers.tpl @@ -0,0 +1,97 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "config.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "config.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "config.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* + Common labels +*/}} +{{- define "config.labels" -}} +app: {{ .Release.Name }}-{{ include "config.name" . }}-init-load +helm.sh/chart: {{ include "config.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Create user custom defined envs +*/}} +{{- define "config.usr-envs"}} +{{- range $key, $val := .Values.usrEnvs.normal }} +- name: {{ $key }} + value: {{ $val }} +{{- end }} +{{- end }} + +{{/* +Create user custom defined secret envs +*/}} +{{- define "config.usr-secret-envs"}} +{{- range $key, $val := .Values.usrEnvs.secret }} +- name: {{ $key }} + valueFrom: + secretKeyRef: + name: {{ $.Release.Name }}-{{ $.Chart.Name }}-user-custom-envs + key: {{ $key }} +{{- end }} +{{- end }} + +{{/* +Create optional scopes list +*/}} +{{- define "config.optionalScopes"}} +{{ $newList := list }} +{{- if eq .Values.configmap.cnCacheType "REDIS" }} +{{ $newList = append $newList ("redis" | quote ) }} +{{- end}} +{{ if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} +{{ $newList = append $newList ("couchbase" | quote) }} +{{- end}} +{{ if eq .Values.global.cnPersistenceType "sql" }} +{{ $newList = append $newList ("sql" | quote) }} +{{- end }} +{{- if .Values.global.opendj.enabled}} +{{ $newList = append $newList ("ldap" | quote) }} +{{- end}} +{{- if .Values.global.fido2.enabled}} +{{ $newList = append $newList ("fido2" | quote) }} +{{- end}} +{{- if .Values.global.scim.enabled}} +{{ $newList = append $newList ("scim" | quote) }} +{{- end}} +{{- if index .Values "global" "client-api" "enabled"}} +{{ $newList = append $newList ("client-api" |quote) }} +{{- end}} +{{ toJson $newList }} +{{- end }} \ No newline at end of file diff --git a/charts/jans/charts/config/templates/clusterrolebinding.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/clusterrolebinding.yaml similarity index 61% rename from charts/jans/charts/config/templates/clusterrolebinding.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/clusterrolebinding.yaml index c0384fa5327..f85789872ab 100644 --- a/charts/jans/charts/config/templates/clusterrolebinding.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/clusterrolebinding.yaml @@ -1,9 +1,19 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: {{ .Release.Name }}-{{ .Release.Namespace }}-cluster-admin-binding + labels: + APP_NAME: configurator +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole @@ -23,6 +33,10 @@ metadata: labels: app: {{ include "config.name" . }}-load name: {{ .Release.Name }}-{{ .Release.Namespace }}-rolebinding +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole diff --git a/charts/jans/charts/config/templates/configmaps.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/configmaps.yaml similarity index 69% rename from charts/jans/charts/config/templates/configmaps.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/configmaps.yaml index c2e218ff736..412e3d0afa9 100644 --- a/charts/jans/charts/config/templates/configmaps.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/configmaps.yaml @@ -1,10 +1,20 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-config-cm namespace: {{ .Release.Namespace }} + labels: + APP_NAME: configurator +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} data: # Jetty header size in bytes in the auth server CN_JETTY_REQUEST_HEADER_SIZE: {{ .Values.configmap.cnJettyRequestHeaderSize | quote }} @@ -53,10 +63,55 @@ data: CN_CONTAINER_METADATA: {{ .Values.configmap.containerMetadataName | quote }} CN_MAX_RAM_PERCENTAGE: {{ .Values.configmap.cnMaxRamPercent | quote }} CN_CACHE_TYPE: {{ .Values.configmap.cnCacheType | quote }} + {{- if not .Values.global.jackrabbit.enabled }} + CN_DOCUMENT_STORE_TYPE: LOCAL + {{- else }} + CN_DOCUMENT_STORE_TYPE: {{ .Values.configmap.cnDocumentStoreType | quote }} + {{- end }} + CN_JACKRABBIT_SYNC_INTERVAL: {{ .Values.configmap.cnJackrabbitSyncInterval | quote }} + {{- if .Values.configmap.cnJackrabbitUrl }} + CN_JACKRABBIT_URL: {{ .Values.configmap.cnJackrabbitUrl | quote }} + {{- else }} + CN_JACKRABBIT_URL: {{ cat "http://" ( .Values.global.jackrabbit.jackRabbitServiceName ) ":8080" | quote | nospace }} + {{- end }} DOMAIN: {{ .Values.global.fqdn | quote }} CN_AUTH_SERVER_BACKEND: {{ cat ( index .Values "global" "auth-server" "authServerServiceName" ) ":8080" | quote | nospace }} + CN_AUTH_APP_LOGGERS: {{ index .Values "global" "auth-server" "appLoggers" + | toJson + | replace "authLogTarget" "auth_log_target" + | replace "authLogLevel" "auth_log_level" + | replace "httpLogTarget" "http_log_target" + | replace "httpLogLevel" "http_log_level" + | replace "persistenceLogTarget" "persistence_log_target" + | replace "persistenceLogLevel" "persistence_log_level" + | replace "persistenceDurationLogTarget" "persistence_duration_log_target" + | replace "persistenceDurationLogLevel" "persistence_duration_log_level" + | replace "ldapStatsLogTarget" "ldap_stats_log_target" + | replace "ldapStatsLogLevel" "ldap_stats_log_level" + | replace "scriptLogTarget" "script_log_target" + | replace "scriptLogLevel" "script_log_level" + | replace "auditStatsLogTarget" "audit_log_target" + | replace "auditStatsLogLevel" "audit_log_level" + | squote + }} + {{- if index .Values "global" "client-api" "enabled" }} CN_CLIENT_API_SERVER_URL: {{ cat ( index .Values "global" "client-api" "clientApiServerServiceName" ) ":8443" | quote | nospace }} CN_CLIENT_API_BIND_IP_ADDRESSES: {{ .Values.configmap.cnClientApiBindIpAddresses | quote }} + CN_CLIENT_API_APP_LOGGERS: {{ index .Values "global" "client-api" "appLoggers" + | toJson + | replace "clientApiLogTarget" "client_api_log_target" + | replace "clientApiLogLevel" "client_api_log_level" + | squote + }} + {{- end }} + {{- if index .Values "global" "config-api" "enabled" }} + CN_CONFIG_API_APP_LOGGERS: {{ index .Values "global" "config-api" "appLoggers" + | toJson + | replace "configApiLogTarget" "config_api_log_target" + | replace "configApiLogLevel" "config_api_log_level" + | squote + }} + {{- end }} {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} LB_ADDR: {{ .Values.configmap.lbAddr }} {{- end }} @@ -90,7 +145,24 @@ data: CN_CERT_ALT_NAME: {{ .Values.global.opendj.ldapServiceName }} #{{ template "cn.fullname" . }}-service CN_PERSISTENCE_LDAP_MAPPING: {{ .Values.configmap.cnPersistenceLdapMapping | quote }} {{- end }} + CN_OXTRUST_CONFIG_GENERATION: {{ .Values.cnOxtrustConfigGeneration | quote }} + {{ if .Values.global.cnJackrabbitCluster }} + CN_JACKRABBIT_ADMIN_ID: {{ .Values.configmap.cnJackrabbitAdminId | quote }} + CN_JACKRABBIT_ADMIN_PASSWORD_FILE: {{ .Values.configmap.cnJackrabbitAdminPasswordFile | quote }} + CN_JACKRABBIT_CLUSTER: {{ .Values.global.cnJackrabbitCluster | quote }} + CN_JACKRABBIT_POSTGRES_USER: {{ .Values.configmap.cnJackrabbitPostgresUser | quote }} + CN_JACKRABBIT_POSTGRES_PASSWORD_FILE: {{ .Values.configmap.cnJackrabbitPostgresPasswordFile | quote }} + CN_JACKRABBIT_POSTGRES_HOST: {{ .Values.configmap.cnJackrabbitPostgresHost | quote }} + CN_JACKRABBIT_POSTGRES_PORT: {{ .Values.configmap.cnJackrabbitPostgresPort | quote }} + CN_JACKRABBIT_POSTGRES_DATABASE: {{ .Values.configmap.cnJackrabbitPostgresDatabaseName | quote }} + # CN_JACKRABBIT_PASSWORD_FILE: {{ .Values.configmap.cnJcaPasswordFile | quote }} NOT IMPLEMENTED + {{- end }} # Auto enable installation of some services + CN_CASA_ENABLED: {{ .Values.configmap.cnCasaEnabled | quote }} + CN_PASSPORT_ENABLED: {{ .Values.configmap.cnPassportEnabled | quote }} + {{- if .Values.global.oxshibboleth.enabled }} + CN_SAML_ENABLED: {{ .Values.configmap.cnSamlEnabled | quote }} + {{- end }} CN_CLIENT_API_APPLICATION_CERT_CN: {{ .Values.configmap.cnClientApiApplicationCertCn | quote }} CN_CLIENT_API_ADMIN_CERT_CN: {{ .Values.configmap.cnClientApiAdminCertCn | quote }} {{ if eq .Values.configmap.cnCacheType "REDIS" }} @@ -107,8 +179,36 @@ data: {{- if .Values.global.scim.enabled }} CN_SCIM_ENABLED: {{ .Values.global.scim.enabled | quote }} CN_SCIM_PROTECTION_MODE: {{ .Values.configmap.cnScimProtectionMode | quote }} + CN_SCIM_APP_LOGGERS: {{ .Values.global.scim.appLoggers + | toJson + | replace "scimLogTarget" "scim_log_target" + | replace "scimLogLevel" "scim_log_level" + | replace "persistenceLogTarget" "persistence_log_target" + | replace "persistenceLogLevel" "persistence_log_level" + | replace "persistenceDurationLogTarget" "persistence_duration_log_target" + | replace "persistenceDurationLogLevel" "persistence_duration_log_level" + | replace "ldapStatsLogTarget" "ldap_stats_log_target" + | replace "ldapStatsLogLevel" "ldap_stats_log_level" + | replace "scriptLogTarget" "script_log_target" + | replace "scriptLogLevel" "script_log_level" + | squote + }} + {{- end }} + {{- if .Values.global.fido2.enabled }} + CN_FIDO2_APP_LOGGERS: {{ .Values.global.fido2.appLoggers + | toJson + | replace "fido2LogTarget" "fido2_log_target" + | replace "fido2LogLevel" "fido2_log_level" + | replace "persistenceLogTarget" "persistence_log_target" + | replace "persistenceLogLevel" "persistence_log_level" + | squote + }} + {{- end }} + {{- if index .Values "global" "admin-ui" "enabled" }} + # ADMIN-UI + ADMIN_UI_JWKS: {{ cat "http://" ( index .Values "global" "auth-server" "authServerServiceName" ) ":8080/jans-auth/restv1/jwks" | quote | nospace }} + CN_CONFIG_API_PLUGINS: "admin-ui,scim" {{- end }} - --- apiVersion: v1 @@ -165,12 +265,12 @@ data: logger.exception(e) return False - # check if jans secret exists + # check if gluu secret exists def get_certs(secret_name, namespace): """ :param namespace: - :return: ssl cert and key from jans secrets + :return: ssl cert and key from gluu secrets """ ssl_cert = None ssl_key = None @@ -211,6 +311,15 @@ kind: ConfigMap metadata: name: {{ include "config.fullname" . }}-tls-script namespace: {{ .Release.Namespace }} + labels: +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} --- @@ -223,7 +332,7 @@ data: # Update the IP of the load balancer automatically """ - License terms and conditions for Janssen Cloud Native Edition: + License terms and conditions for Gluu Cloud Native Edition: https://www.apache.org/licenses/LICENSE-2.0 """ @@ -268,7 +377,7 @@ data: try: while True: lb_addr = os.environ.get("LB_ADDR", "") - domain = os.environ.get("DOMAIN", "demoexample.jans.io") + domain = os.environ.get("DOMAIN", "demoexample.gluu.org") host_file = open('/etc/hosts', 'r').readlines() hosts = get_hosts(lb_addr, domain) stop = [] @@ -299,3 +408,12 @@ kind: ConfigMap metadata: name: {{ .Release.Name }}-updatelbip namespace: {{ .Release.Namespace }} + labels: +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} \ No newline at end of file diff --git a/charts/jans/charts/config/templates/load-init-config.yml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/load-init-config.yml similarity index 89% rename from charts/jans/charts/config/templates/load-init-config.yml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/load-init-config.yml index aafaa1257c1..0cf54d56510 100644 --- a/charts/jans/charts/config/templates/load-init-config.yml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/load-init-config.yml @@ -1,5 +1,4 @@ -{{- if ( not .Values.global.upgrade.enabled ) }} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: batch/v1 kind: Job @@ -7,14 +6,22 @@ metadata: name: {{ include "config.fullname" . }} namespace: {{ .Release.Namespace }} labels: - APP_NAME: configuration-manager -{{ include "config.labels" . | indent 4}} + APP_NAME: configurator +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: + ttlSecondsAfterFinished: 120 template: metadata: name: {{ include "config.name" . }}-job labels: - APP_NAME: configuration-manager + APP_NAME: configurator app: {{ .Release.Name }}-{{ include "config.name" . }}-init-load spec: {{- with .Values.image.pullSecrets }} @@ -95,4 +102,3 @@ spec: curl -X POST http://localhost:15020/quitquitquit {{- end }} restartPolicy: Never -{{- end }} diff --git a/charts/jans/charts/config/templates/rolebinding.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/rolebinding.yaml similarity index 58% rename from charts/jans/charts/config/templates/rolebinding.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/rolebinding.yaml index 31a2ec5ff6a..54ab7ef8cd0 100644 --- a/charts/jans/charts/config/templates/rolebinding.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/rolebinding.yaml @@ -1,10 +1,20 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: {{ .Release.Name }}-{{ .Release.Namespace }}-rolebinding namespace: {{ .Release.Namespace }} + labels: + APP_NAME: configurator +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} subjects: - kind: User name: system:serviceaccount:{{ .Release.Namespace }}:default # Name is case sensitive diff --git a/charts/jans/charts/config/templates/roles.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/roles.yaml similarity index 51% rename from charts/jans/charts/config/templates/roles.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/roles.yaml index b9946cfbfc8..efa403d47c8 100644 --- a/charts/jans/charts/config/templates/roles.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/roles.yaml @@ -1,10 +1,20 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: {{ .Release.Name }}-{{ .Release.Namespace }}-cn-role namespace: {{ .Release.Namespace }} + labels: + APP_NAME: configurator +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} rules: - apiGroups: [""] # "" refers to the core API group resources: ["configmaps", "secrets"] diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/secrets.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/secrets.yaml new file mode 100644 index 00000000000..0c730378f6b --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/secrets.yaml @@ -0,0 +1,195 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "config.fullname" . }}-gen-json-file + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: configurator +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +stringData: + generate.json: |- + { + "hostname": {{ .Values.global.fqdn | quote }}, + "country_code": {{ .Values.countryCode | quote }}, + "state": {{ .Values.state | quote }}, + "city": {{ .Values.city | quote }}, + "admin_pw": {{ .Values.adminPassword | quote }}, + "ldap_pw": {{ .Values.ldapPassword | quote }}, + "redis_pw": {{ .Values.redisPassword | quote }}, + "email": {{ .Values.email | quote }}, + "org_name": {{ .Values.orgName | quote }}, + {{ if eq .Values.global.cnPersistenceType "sql" }} + "sql_pw": {{ .Values.configmap.cnSqldbUserPassword | quote }}, + {{- end }} + {{ if or ( eq .Values.global.cnPersistenceType "couchbase" ) ( eq .Values.global.cnPersistenceType "hybrid" ) }} + "couchbase_pw": {{ .Values.configmap.cnCouchbasePassword | quote }}, + "couchbase_superuser_pw": {{ .Values.configmap.cnCouchbaseSuperUserPassword | quote }}, + {{- end }} + "auth_sig_keys": {{ index .Values "global" "auth-server" "authSigKeys" | quote }}, + "auth_enc_keys": {{ index .Values "global" "auth-server" "authEncKeys" | quote }}, + "optional_scopes": {{ list (include "config.optionalScopes" . | fromJsonArray | join ",") }} + } + +{{ if or ( eq .Values.global.cnPersistenceType "couchbase" ) ( eq .Values.global.cnPersistenceType "hybrid" ) }} +{{- if not .Values.global.istio.enabled }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-cb-crt + labels: +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + couchbase.crt: {{ .Values.configmap.cnCouchbaseCrt }} +{{- end }} +{{- end }} +{{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-google-sa + labels: +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + google-credentials.json: {{ .Values.configmap.cnGoogleSecretManagerServiceAccount }} +{{- end}} + +{{ if .Values.global.cnObExtSigningJwksCrt }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-ob-ext-signing-jwks-crt-key-pin + labels: +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} + namespace: {{ .Release.Namespace }} +type: Opaque +data: + ob-ext-signing.crt: {{ .Values.global.cnObExtSigningJwksCrt }} + {{ if .Values.global.cnObExtSigningJwksKey }} + ob-ext-signing.key: {{ .Values.global.cnObExtSigningJwksKey }} + {{- end }} + {{ if .Values.global.cnObExtSigningJwksKeyPassPhrase }} + ob-ext-signing.pin: {{ .Values.global.cnObExtSigningJwksKeyPassPhrase }} + {{- end }} +{{- end }} +{{ if .Values.global.cnObTransportCrt }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-ob-transport-crt-key-pin + labels: +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} + namespace: {{ .Release.Namespace }} +type: Opaque +data: + ob-transport.crt: {{ .Values.global.cnObTransportCrt }} + {{ if .Values.global.cnObTransportKey }} + ob-transport.key: {{ .Values.global.cnObTransportKey }} + {{- end }} + {{ if .Values.global.cnObTransportKeyPassPhrase }} + ob-transport.pin: {{ .Values.global.cnObTransportKeyPassPhrase }} + {{- end }} +{{- end }} +{{ if .Values.global.cnObTransportTrustStore }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-ob-transport-truststore + labels: +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} + namespace: {{ .Release.Namespace }} +type: Opaque +data: + ob-transport-truststore.p12: {{ .Values.global.cnObTransportTrustStore }} +{{- end }} +{{- if or (eq .Values.global.cnPersistenceType "ldap") (eq .Values.global.cnPersistenceType "hybrid") }} +--- +# Consider removing secret after moving ldapPass to global. This is only used by the cronJob ldap backup. +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-ldap-cron-pass + labels: +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +data: + password: {{ .Values.ldapPassword | b64enc }} +{{- end}} +{{- if index .Values "global" "admin-ui" "enabled" }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-admin-ui-license + labels: +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +data: + admin_ui_api_key: {{ index .Values "global" "admin-ui" "adminUiApiKey" | b64enc }} + admin_ui_product_code: {{ index .Values "global" "admin-ui" "adminUiProductCode" | b64enc }} + admin_ui_shared_key: {{ index .Values "global" "admin-ui" "adminUiSharedKey" | b64enc }} + admin_ui_management_key: {{ index .Values "global" "admin-ui" "adminUiManagementKey" | b64enc }} +{{- end}} \ No newline at end of file diff --git a/charts/jans/charts/config/templates/service.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/service.yaml similarity index 51% rename from charts/jans/charts/config/templates/service.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/service.yaml index d11bf7a30c9..da5dedf8914 100644 --- a/charts/jans/charts/config/templates/service.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/service.yaml @@ -5,9 +5,17 @@ apiVersion: v1 kind: Service metadata: - name: {{ include "config.fullname" . }} - labels: -{{ include "config.labels" . | indent 6 }} + name: {{ include "config.fullname" . }} + labels: + APP_NAME: configurator +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: ports: - name: http diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/upgrade-ldap-101-jans.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/upgrade-ldap-101-jans.yaml new file mode 100644 index 00000000000..83b4e9b6157 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/upgrade-ldap-101-jans.yaml @@ -0,0 +1,1778 @@ +{{- if .Values.global.upgrade.enabled }} +{{- if or (eq .Values.global.cnPersistenceType "ldap") (eq .Values.global.cnPersistenceType "hybrid") }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-oxjans + namespace: {{ .Release.Namespace }} + labels: +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} + annotations: + "helm.sh/hook": pre-upgrade + "helm.sh/hook-weight": "0" + "helm.sh/hook-delete-policy": before-hook-creation +{{- if .Values.additionalAnnotations }} +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +data: + 101-jans.ldif: |+ + dn: cn=schema + objectClass: top + objectClass: ldapSubentry + objectClass: subschema + cn: schema + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.1 NAME 'jansAssociatedClnt' + DESC 'Associate the dn of an OAuth2 client with a person or UMA Resource Set.' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.2 NAME 'county' + DESC 'ISO 3166-1 Alpha-2 Country Code' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.3 NAME 'creationDate' + DESC 'Creation Date used for password reset requests' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.4 NAME 'jansDefScope' + DESC 'Track the default scope for an custom OAuth2 Scope.' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.5 NAME 'jansAttrViewTyp' + DESC 'Specify in exclude who can view an attribute, admin or user' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.6 NAME 'jansAttrEditTyp' + DESC 'Specify in exclude who can update an attribute, admin or user' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.7 NAME 'jansAttrName' + DESC 'Specify an identifier for an attribute. May be multi-value where an attribute has two names, like givenName and first-name.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.8 NAME 'jansAttrOrigin' + DESC 'Specify the person objectclass associated with the attribute, used for display purposes in exclude.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.9 NAME 'jansAttrSystemEditTyp' + DESC 'TODO - still required?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.10 NAME 'jansAttrTyp' + DESC 'Data type of attribute. Values can be string, photo, numeric, date' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.11 NAME 'jansAttrUsgTyp' + DESC 'TODO - Usg? Value can be OpenID' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.12 NAME 'jansCustomMessage' + DESC 'exclude custom welcome message' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.13 NAME 'jansFaviconImage' + DESC 'TODO - Stores URL of favicon' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.14 NAME 'jansHostname' + DESC 'The hostname of the Jans Server instance' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.15 NAME 'jansIpAddr' + DESC 'IP address of the Jans Server instance' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.16 NAME 'jansLastUpd' + DESC 'Monitors last time the server was able to connect to the monitoring system.' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.17 NAME 'jansLogoImage' + DESC 'Logo used by exclude for default look and feel.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.18 NAME 'jansManagedOrganizations' + DESC 'Used to track with which organizations a person is associated' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.19 NAME 'jansManager' + DESC 'Used to specify if a person has the manager role' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.20 NAME 'jansManagerGrp' + DESC 'Used in organizatoin entry to specifies the dn of the group that has admin priviledges in exclude.' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.21 NAME 'jansOptOuts' + DESC 'White pages attributes restricted by person in exclude profile management' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.22 NAME 'jansOrgProfileMgt' + DESC 'enable or disable profile management feature in exclude' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.23 NAME 'jansOrgShortName' + DESC 'Short description, as few letters as possible, no spaces.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.24 NAME 'jansSAML1URI' + DESC 'SAML 1 uri of attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.25 NAME 'jansSAML2URI' + DESC 'SAML 2 uri of attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.26 NAME 'jansScimEnabled' + DESC 'exclude SCIM feature - enabled or disabled' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.27 NAME 'jansSslExpiry' + DESC 'SAML Trust Relationship configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.28 NAME 'jansStatus' + DESC 'Status of the entry, used by many objectclasses' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.29 NAME 'jansThemeColor' + DESC 'exclude login page configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.30 NAME 'jansUrl' + DESC 'Jans instance URL' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.31 NAME 'inum' + DESC 'XRI i-number' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.32 NAME 'memberOf' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.33 NAME 'jansAmHost' + DESC 'am host' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.34 NAME 'jansClaimName' + DESC 'Used by jans in conjunction with jansttributeName to map claims to attributes in LDAP.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.35 NAME 'jansAppTyp' + DESC 'jans App Typ' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.36 NAME 'authnTime' + DESC 'jans Authn Time' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.37 NAME 'authzCode' + DESC 'jans authorization code' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.38 NAME 'jansClaim' + DESC 'jans Attr Claim' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.39 NAME 'jansGrpClaims' + DESC 'jans Grp Attr Claims (true or false)' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.40 NAME 'jansClntId' + DESC 'jans Clnt id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.41 NAME 'clnId' + DESC 'jans Clnt id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.42 NAME 'jansClntIdIssuedAt' + DESC 'jans Clnt Issued At' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.43 NAME 'jansClntSecret' + DESC 'jans Clnt Secret' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.44 NAME 'jansClntSecretExpAt' + DESC 'Date client expires' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.45 NAME 'jansClntURI' + DESC 'jans Clnt URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.46 NAME 'jansConfDyn' + DESC 'jans Dyn Conf' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.47 NAME 'jansConfErrors' + DESC 'jans Errors Conf' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.48 NAME 'jansConfStatic' + DESC 'jans Static Conf' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.49 NAME 'jansConfWebKeys' + DESC 'jans Web Keys Conf' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.50 NAME 'jansContact' + DESC 'jans Contact' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.51 NAME 'iat' + DESC 'jans Creation' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.52 NAME 'jansDefAcrValues' + DESC 'jans Def Acr Values' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.53 NAME 'jansDefMaxAge' + DESC 'jans Def Max Age' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.54 NAME 'exp' + DESC 'jans Exp' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.55 NAME 'grtId' + DESC 'jans grant id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.56 NAME 'jansGrantTyp' + DESC 'jans Grant Typ' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.57 NAME 'grtTyp' + DESC 'jans Grant Typ' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.58 NAME 'jansIdTknEncRespAlg' + DESC 'jans ID Tkn Enc Resp Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.59 NAME 'jansIdTknEncRespEnc' + DESC 'jans ID Tkn Enc Resp Enc' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.60 NAME 'jansIdTknSignedRespAlg' + DESC 'jans ID Tkn Signed Resp Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.61 NAME 'jansInitiateLoginURI' + DESC 'jans Initiate Login URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.62 NAME 'jansJwksURI' + DESC 'jans JWKs URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.63 NAME 'jansJwks' + DESC 'jans JWKs' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.64 NAME 'jwtReq' + DESC 'jans JWT Req' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.65 NAME 'jansLogoURI' + DESC 'jans Logo URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.66 NAME 'nnc' + DESC 'jans nonce' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.67 NAME 'jansSessState' + DESC 'jans Sess State' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.68 NAME 'jansPermissionGrantedMap' + DESC 'jans Permission Granted Map' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.69 NAME 'jansPersistentJWT' + DESC 'jans Persistent JWT' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.70 NAME 'jansPolicyURI' + DESC 'jans Policy URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.71 NAME 'jansLogoutURI' + DESC 'jans Policy URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.72 NAME 'jansLogoutSessRequired' + DESC 'jans Policy URI' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.73 NAME 'jansPostLogoutRedirectURI' + DESC 'jans Post Logout Redirect URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.74 NAME 'jansRedirectURI' + DESC 'jans Redirect URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.75 NAME 'jansRegistrationAccessTkn' + DESC 'jans Registration Access Tkn' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.76 NAME 'jansReleasedScope' + DESC 'jans released scope attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.77 NAME 'jansReqObjSigAlg' + DESC 'jans Req Obj Sig Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.78 NAME 'jansReqObjEncAlg' + DESC 'jans Req Obj Enc Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.79 NAME 'jansReqObjEncEnc' + DESC 'jans Req Obj Enc Enc' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.80 NAME 'jansReqURI' + DESC 'jans Req URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.81 NAME 'jansRequireAuthTime' + DESC 'jans Require Authn Time' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.82 NAME 'jansRespTyp' + DESC 'jans Resp Typ' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.83 NAME 'jansScope' + DESC 'jans Attr Scope' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.84 NAME 'scp' + DESC 'jans Attr Scope' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.85 NAME 'jansScopeTyp' + DESC 'OX Attr Scope type' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.86 NAME 'jansSectorIdentifierURI' + DESC 'jans Sector Identifier URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.87 NAME 'jansSignedRespAlg' + DESC 'jans Signed Resp Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.88 NAME 'jansSkipAuthz' + DESC 'jans skip authorization attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.89 NAME 'jansSubjectTyp' + DESC 'jans Subject Typ' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.90 NAME 'tknCde' + DESC 'jans Tkn Code' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.91 NAME 'jansTknEndpointAuthMethod' + DESC 'jans Tkn Endpoint Auth Method' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.92 NAME 'jansTknEndpointAuthSigAlg' + DESC 'jans Tkn Endpoint Auth Sig Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.93 NAME 'tknTyp' + DESC 'jans Tkn Typ' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.94 NAME 'jansTosURI' + DESC 'jans TOS URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.95 NAME 'jansTrustedClnt' + DESC 'jans Trusted Clnt' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.96 NAME 'jansUmaScope' + DESC 'URI reference of scope descriptor' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.97 NAME 'jansUsrDN' + DESC 'jans Usr DN' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.98 NAME ( 'jansUsrId' 'usrId' ) + DESC 'jans user id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.99 NAME 'jansUsrInfEncRespAlg' + DESC 'jans Usr Inf Enc Resp Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.100 NAME 'jansUsrInfEncRespEnc' + DESC 'jans Usr Inf Enc Resp Enc' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.101 NAME 'jansExtraConf' + DESC 'jans additional configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.102 NAME 'jansAuthMode' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.103 NAME 'acr' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.104 NAME 'jansConfCode' + DESC 'jans configuration code' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.105 NAME 'jansCreationTimestamp' + DESC 'Registration time' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.106 NAME 'jansExtUid' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.107 NAME 'jansOTPCache' + DESC 'Stores a used OTP to prevent a hacker from using it again. Complementary to jansExtUid attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.108 NAME 'jansGrp' + DESC 'Usr group' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.109 NAME 'jansGuid' + DESC 'A random string to mark temporary tokens' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.110 NAME 'uuid' + DESC 'Unique identifier' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.111 NAME 'jansHost' + DESC 'jans host' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.112 NAME 'jansDbAuth' + DESC 'Custom IDP authentication configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.113 NAME 'jansIconUrl' + DESC 'jans icon url' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.114 NAME 'jansId' + DESC 'Identifier' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.115 NAME 'sid' + DESC 'Sess Identifier' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.116 NAME 'jansAsJwt' + DESC 'Boolean field to indicate whether object is used as JWT' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.117 NAME 'jansJwt' + DESC 'JWT representation of the object or otherwise jwt associated with the object' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.118 NAME 'jansInvolvedClnts' + DESC 'Involved clients' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.119 NAME 'jansLastAccessTime' + DESC 'Last access time' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.120 NAME 'jansLastLogonTime' + DESC 'Last logon time' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.121 NAME 'jansLogViewerConfig' + DESC 'Log viewer configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.122 NAME 'jansMultivaluedAttr' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.123 NAME 'jansName' + DESC 'Name' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.124 NAME 'jansNameIdTyp' + DESC 'NameId Typ' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.125 NAME 'jansPolicyRule' + DESC 'Policy Rule' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.126 NAME 'jansUmaPolicyScrDn' + DESC 'OX policy script Dn' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.127 NAME 'jansState' + DESC 'jansState' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.128 NAME 'jansCounter' + DESC 'jansCounter' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.129 NAME 'jansApp' + DESC 'jansApp' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.130 NAME 'jansDeviceRegistrationConf' + DESC 'jansDeviceRegistrationConf' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.131 NAME 'jansDeviceKeyHandle' + DESC 'jansDeviceKeyHandle' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.132 NAME 'jansDeviceHashCode' + DESC 'jansDeviceHashCode' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.133 NAME 'jansReq' + DESC 'jansReq' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.134 NAME 'jansReqId' + DESC 'jansReqId' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.135 NAME 'jansDeviceData' + DESC 'jansDeviceData' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.136 NAME 'jansEnrollmentCode' + DESC 'jansEnrollmentCode' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.137 NAME 'jansPushApp' + DESC 'jansPush application DN' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.138 NAME 'jansPushAppConf' + DESC 'jansPush application configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.139 NAME 'jansPushDeviceConf' + DESC 'jansPush device configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.140 NAME 'jansRegistrationConf' + DESC 'Registration Conf' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.141 NAME 'jansResource' + DESC 'Host path' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.142 NAME 'jansResourceSetId' + DESC 'jans resource set id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.143 NAME 'jansRevision' + DESC 'Revision' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.144 NAME 'jansLevel' + DESC 'Level' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.145 NAME 'jansScimCustomAttr' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.146 NAME 'jansScr' + DESC 'Attr that contains script (python, java script)' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.147 NAME 'jansScrDn' + DESC 'Script object DN' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.148 NAME 'jansScrTyp' + DESC 'Attr that contains script type (e.g. python, java script)' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.149 NAME 'jansScrError' + DESC 'Attr that contains first error which application get during it execution' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.150 NAME 'jansSmtpConf' + DESC 'SMTP configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.151 NAME 'jansSourceAttr' + DESC 'Source Attr for this Attr' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.152 NAME 'jansTicket' + DESC 'jans ticket' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.153 NAME 'jansActive' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.154 NAME 'jansAddres' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.155 NAME 'jansConfApp' + DESC 'jans App Conf' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.156 NAME 'jansEmail' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.157 NAME 'jansEntitlements' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.158 NAME 'jansExtId' + EQUALITY caseExactMatch + SUBSTR caseExactSubStringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.159 NAME 'jansImsValue' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.160 NAME 'jansMetaCreated' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.161 NAME 'jansMetaLastMod' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.162 NAME 'jansMetaLocation' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.163 NAME 'jansMetaVer' + EQUALITY caseExactMatch + SUBSTR caseExactSubStringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.164 NAME 'jansNameFormatted' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.165 NAME 'jansPhoneValue' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.166 NAME 'jansPhotos' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.167 NAME 'jansProfileURL' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.168 NAME 'jansRole' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.169 NAME 'jansTitle' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.170 NAME 'jansUsrTyp' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.171 NAME 'jansHonorificPrefix' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.172 NAME 'jansHonorificSuffix' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.173 NAME 'jans509Certificate' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.174 NAME 'jansTyp' + DESC 'jans type' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.175 NAME 'jansUmaPermission' + DESC 'jans uma permission' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.176 NAME 'persistentId' + DESC 'PersistentId' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Persistent ID reserved for SAML' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.177 NAME 'personInum' + DESC 'Inum of a person' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.178 NAME 'jansProgLng' + DESC 'programming language' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.179 NAME 'registrationDate' + DESC 'Registration date' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.180 NAME 'role' + DESC 'Role' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.181 NAME 'secretAnswer' + DESC 'Secret Answer' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.182 NAME 'secretQuestion' + DESC 'Secret Question' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.183 NAME 'jansSoftVer' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.184 NAME 'transientId' + DESC 'TransientId' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.185 NAME 'url' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.186 NAME 'urn' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.187 NAME ( 'middleName' 'excludeMiddleName' ) + DESC 'Middle name(s)' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.188 NAME ( 'nickname' 'excludenickname' ) + DESC 'Casual name of the End-Usr' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.189 NAME 'jansPrefUsrName' + DESC 'Shorthand Name' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.190 NAME 'profile' + DESC 'Profile page URL of the person' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.191 NAME ( 'picture' 'photo1' ) + DESC 'Profile picture URL of the person' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.192 NAME 'website' + DESC 'Web page or blog URL of the person' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.193 NAME 'emailVerified' + DESC 'True if the e-mail address of the person has been verified; otherwise false' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.194 NAME 'gender' + DESC 'Gender of the person either female or male' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.195 NAME 'birthdate' + DESC 'Birthday of the person, represented as an ISO 8601:2004 [ISO8601‑2004] YYYY-MM-DD format' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.196 NAME ( 'zoneinfo' 'timezone' ) + DESC 'Time zone database representing the End-Usrs time zone. For example, Europe/Paris or America/Los_Angeles' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.197 NAME ( 'locale' 'excludeLocale' ) + DESC 'Locale of the person, represented as a BCP47 [RFC5646] language tag' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.198 NAME 'phoneNumberVerified' + DESC 'True if the phone number of the person has been verified, otherwise false' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.199 NAME 'address' + DESC 'OpenID Connect formatted JSON object representing the address of the person' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.200 NAME 'updatedAt' + DESC 'Time the information of the person was last updated. Seconds from 1970-01-01T0:0:0Z' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.201 NAME 'jansRegExp' + DESC 'Regular expression used to validate attribute data' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.202 NAME 'jansTooltip' + DESC 'Custom tooltip to be shown on the UI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.203 NAME 'jansModuleProperty' + DESC 'Module property' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.204 NAME 'jansConfProperty' + DESC 'Conf property' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.205 NAME 'jansSessAttr' + DESC 'jansSessAttr' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.206 NAME 'jansStartDate' + DESC 'Start date' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.207 NAME 'jansEndDate' + DESC 'End date' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.208 NAME 'jansMetricTyp' + DESC 'Metric type' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.209 NAME 'jansData' + DESC 'OX data' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.210 NAME 'dat' + DESC 'OX data' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.211 NAME 'jansCodeChallenge' + DESC 'OX PKCE code challenge' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.212 NAME 'chlng' + DESC 'OX PKCE code challenge' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.213 NAME 'chlngMth' + DESC 'OX PKCE code challenge method' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.214 NAME 'jansSectorIdentifier' + DESC 'jans Sector Identifier' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.215 NAME 'jansPersistClntAuthzs' + DESC 'jans Persist Clnt Authzs' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.216 NAME 'jansSessStateId' + DESC 'jansSessStateId' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.217 NAME 'ssnId' + DESC 'jans Sess DN' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.218 NAME 'jansPassExpDate' + DESC 'Pass Exp date, represented as an ISO 8601 (YYYY-MM-DD) format' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.219 NAME 'jansCountInvalidLogin' + DESC 'Invalid login attempts count' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.220 NAME 'jansIMAPData' + DESC 'This data has information about your imap connection' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.221 NAME 'jansValidation' + DESC 'This data has information about attribute Validation' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.222 NAME 'jansPPID' + DESC 'Persistent Pairwise ID for OpenID Connect' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.223 NAME 'jansSessId' + DESC 'jans Sess Id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.224 NAME 'jansCacheConf' + DESC 'Cache configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.225 NAME 'jansLogConfigLocation' + DESC 'Path to external log4j2.xml' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.226 NAME 'jansInclClaimsInIdTkn' + DESC 'jans Incl Claims In Id Tkn' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.227 NAME 'jansClaimValues' + DESC 'Claim Values' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.228 NAME 'jansClaimRedirectURI' + DESC 'Claim Redirect URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.229 NAME 'jansAttrs' + DESC 'Attrs' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.230 NAME 'attr' + DESC 'Attrs' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.231 NAME 'jansRefreshTknLife' + DESC 'Life of refresh token' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.232 NAME 'jansPermissionGranted' + DESC 'jans Permission Granted' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.233 NAME 'jansNickName' + DESC 'jansNickName' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.234 NAME 'jansDeviceNotificationConf' + DESC 'Extended push notification configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.235 NAME 'clms' + DESC 'jans Claims' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.236 NAME 'jansDisabled' + DESC 'Status of client' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.237 NAME 'jansWebKeysSettings' + DESC 'jans Web Keys Conf' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.238 NAME 'jansScopeExpression' + DESC 'Scope expression' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.239 NAME 'jansPreferredMethod' + DESC 'Jans Casa - jansPref method to use for user authentication' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.240 NAME 'jansOTPDevices' + DESC 'Jans Casa - Json representation of OTP devices. Complementary to jansExtUid attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.241 NAME 'jansMobileDevices' + DESC 'Jans Casa - Json representation of mobile devices. Complementary to mobile attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.242 NAME 'jansdId' + DESC 'jansd Id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.243 NAME 'jansAuthorizedOrigins' + DESC 'jans Authorized Origins' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.244 NAME 'jansStrongAuthPolicy' + DESC 'Jans Casa - 2FA Enforcement Policy for Usr' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.245 NAME 'tknBndCnf' + DESC 'jansauth - Tkn Binding Id Hash' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.246 NAME 'jansUnlinkedExternalUids' + DESC 'Jans Casa - List of unlinked social accounts (ie disabled jansExtUids)' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.247 NAME 'jansAccessTknAsJwt' + DESC 'jansauth - indicator whether to return access token as JWT' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.248 NAME 'jansAccessTknSigAlg' + DESC 'jansauth - access token signing algorithm' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.249 NAME 'jansRegistrationData' + DESC 'jansRegistrationData' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.250 NAME 'jansAuthData' + DESC 'jansAuthData' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.251 NAME 'jansPublicKeyId' + DESC 'jansPublicKeyId' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.252 NAME 'jansAccessTknLife' + DESC 'Life of access token' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.253 NAME 'jansSoftId' + DESC 'Soft Identifier' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.254 NAME 'jansSoftStatement' + DESC 'Soft Statement' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.255 NAME 'jansRptAsJwt' + DESC 'jansRptAsJwt' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.256 NAME 'jansCodeChallengeHash' + DESC 'OX code challenge hash' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.257 NAME 'del' + DESC 'del' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.258 NAME 'jansEnabled' + DESC 'Status of the entry, used by many objectclasses' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.259 NAME 'jansAlias' + DESC 'jansAlias' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.260 NAME 'jansLogoPath' + DESC 'jansLogoPath' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.261 NAME 'jansFaviconPath' + DESC 'jansFaviconPath' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.262 NAME 'jansBackchannelTknDeliveryMode' + DESC 'jans Backchannel Tkn Delivery Mode' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.263 NAME 'jansBackchannelClntNotificationEndpoint' + DESC 'jans Backchannel Clnt Notification Endpoint' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.264 NAME 'jansBackchannelAuthnReqSigAlg' + DESC 'jans Backchannel Authn Req Sig Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.265 NAME 'jansBackchannelUsrCodeParameter' + DESC 'jans Backchannel Usr Code Parameter' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.266 NAME 'jansBackchannelDeviceRegistrationTkn' + DESC 'jans Backchannel Device Registration Tkn' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.267 NAME 'jansBackchannelUsrCode' + DESC 'jans Backchannel Usr Code' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.268 NAME 'jansDocStoreConf' + DESC 'jansDocStoreConf' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.269 NAME 'authReqId' + DESC 'Authn request id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Jans created attribute' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.1 NAME 'jansPairwiseIdentifier' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansId $ jansSectorIdentifier $ jansClntId $ jansUsrId ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.2 NAME 'jansPerson' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansAssociatedClnt $ c $ displayName $ givenName $ jansManagedOrganizations $ jansOptOuts $ jansStatus $ inum $ mail $ memberOf $ o $ jansPersistentJWT $ jansCreationTimestamp $ jansExtUid $ jansOTPCache $ jansLastLogonTime $ jansActive $ jansAddres $ jansEmail $ jansEntitlements $ jansExtId $ jansImsValue $ jansMetaCreated $ jansMetaLastMod $ jansMetaLocation $ jansMetaVer $ jansNameFormatted $ jansPhoneValue $ jansPhotos $ jansProfileURL $ jansRole $ jansTitle $ jansUsrTyp $ jansHonorificPrefix $ jansHonorificSuffix $ jans509Certificate $ jansPassExpDate $ persistentId $ middleName $ nickname $ jansPrefUsrName $ profile $ picture $ website $ emailVerified $ gender $ birthdate $ zoneinfo $ locale $ phoneNumberVerified $ address $ updatedAt $ preferredLanguage $ role $ secretAnswer $ secretQuestion $ seeAlso $ sn $ cn $ transientId $ uid $ userPassword $ st $ street $ l $ jansCountInvalidLogin $ jansEnrollmentCode $ jansIMAPData $ jansPPID $ jansGuid $ jansPreferredMethod $ userCertificate $ jansOTPDevices $ jansMobileDevices $ jansStrongAuthPolicy $ jansUnlinkedExternalUids $ jansBackchannelDeviceRegistrationTkn $ jansBackchannelUsrCode ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.3 NAME 'jansGrp' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( c $ description $ displayName $ jansStatus $ inum $ member $ o $ owner $ seeAlso $ jansMetaCreated $ jansMetaLastMod $ jansMetaLocation $ jansMetaVer ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.4 NAME 'jansOrganization' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( c $ county $ description $ displayName $ jansCustomMessage $ jansFaviconImage $ jansLogoImage $ jansManager $ jansManagerGrp $ jansOrgShortName $ jansThemeColor $ inum $ l $ mail $ memberOf $ o $ jansCreationTimestamp $ jansRegistrationConf $ postalCode $ st $ street $ telephoneNumber $ title $ uid $ jansLogoPath $ jansFaviconPath ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.5 NAME 'jansAppConf' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( c $ ou $ description $ displayName $ jansHostname $ jansLastUpd $ jansManager $ jansOrgProfileMgt $ jansScimEnabled $ jansEmail $ jansSmtpConf $ jansSslExpiry $ jansStatus $ jansUrl $ inum $ o $ jansAuthMode $ jansDbAuth $ jansLogViewerConfig $ jansLogConfigLocation $ jansCacheConf $ jansDocStoreConf $ jansSoftVer $ userPassword $ jansConfDyn $ jansConfErrors $ jansConfStatic $ jansConfWebKeys $ jansWebKeysSettings $ jansConfApp $ jansRevision ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.6 NAME 'jansAttr' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( description $ displayName $ jansAttrEditTyp $ jansAttrName $ jansAttrOrigin $ jansAttrSystemEditTyp $ jansAttrTyp $ jansClaimName $ jansAttrUsgTyp $ jansAttrViewTyp $ jansSAML1URI $ jansSAML2URI $ jansStatus $ inum $ jansMultivaluedAttr $ jansNameIdTyp $ jansScimCustomAttr $ jansSourceAttr $ seeAlso $ urn $ jansRegExp $ jansTooltip $ jansValidation ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.7 NAME 'jansPassResetReq' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( creationDate $ jansGuid $ personInum ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.8 NAME 'jansEntry' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( displayName $ inum ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.9 NAME 'jansClnt' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( displayName $ description $ inum $ jansAppTyp $ jansClntIdIssuedAt $ jansClntSecret $ jansClntSecretExpAt $ exp $ del $ jansClntURI $ jansContact $ jansDefAcrValues $ jansDefMaxAge $ jansGrantTyp $ jansIdTknEncRespAlg $ jansIdTknEncRespEnc $ jansIdTknSignedRespAlg $ jansInitiateLoginURI $ jansJwksURI $ jansJwks $ jansLogoURI $ jansPolicyURI $ jansPostLogoutRedirectURI $ jansRedirectURI $ jansRegistrationAccessTkn $ jansReqObjSigAlg $ jansReqObjEncAlg $ jansReqObjEncEnc $ jansReqURI $ jansRequireAuthTime $ jansRespTyp $ jansScope $ jansClaim $ jansSectorIdentifierURI $ jansSignedRespAlg $ jansSubjectTyp $ jansTknEndpointAuthMethod $ jansTknEndpointAuthSigAlg $ jansTosURI $ jansTrustedClnt $ jansUsrInfEncRespAlg $ jansUsrInfEncRespEnc $ jansExtraConf $ jansClaimRedirectURI $ jansLastAccessTime $ jansLastLogonTime $ jansPersistClntAuthzs $ jansInclClaimsInIdTkn $ jansRefreshTknLife $ jansDisabled $ jansLogoutURI $ jansLogoutSessRequired $ jansdId $ jansAuthorizedOrigins $ tknBndCnf $ jansAccessTknAsJwt $ jansAccessTknSigAlg $ jansAccessTknLife $ jansSoftId $ jansSoftVer $ jansSoftStatement $ jansRptAsJwt $ jansAttrs $ jansBackchannelTknDeliveryMode $ jansBackchannelClntNotificationEndpoint $ jansBackchannelAuthnReqSigAlg $ jansBackchannelUsrCodeParameter ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.10 NAME 'jansScope' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansDefScope $ description $ displayName $ inum $ jansScopeTyp $ jansClaim $ jansScrDn $ jansGrpClaims $ jansId $ jansIconUrl $ jansUmaPolicyScrDn $ jansAttrs $ exp $ del ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.11 NAME 'jansSessId' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansId $ sid $ creationDate $ exp $ del $ jansLastAccessTime $ jansUsrDN $ authnTime $ jansState $ jansSessState $ jansPermissionGranted $ jansAsJwt $ jansJwt $ jansPermissionGrantedMap $ jansInvolvedClnts $ jansSessAttr ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.12 NAME 'jansUmaResource' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( displayName $ inum $ owner $ jansAssociatedClnt $ jansUmaScope $ jansFaviconImage $ jansGrp $ jansId $ jansResource $ jansRevision $ jansTyp $ jansScopeExpression $ iat $ exp $ del $ description ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.13 NAME 'jansUmaResourcePermission' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( exp $ del $ jansUmaScope $ jansConfCode $ jansResourceSetId $ jansAttrs $ jansTicket $ jansStatus ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.14 NAME 'jansGrant' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( grtId $ iat ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.15 NAME 'jansToken' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( authnTime $ authzCode $ iat $ exp $ del $ grtId $ grtTyp $ jwtReq $ nnc $ scp $ tknCde $ tknTyp $ usrId $ clnId $ acr $ uuid $ chlng $ chlngMth $ clms $ ssnId $ attr $ tknBndCnf ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.16 NAME 'jansUmaRPT' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( authnTime $ clnId $ iat $ exp $ del $ tknCde $ usrId $ jansUmaPermission $ uuid ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.17 NAME 'jansScr' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( inum $ jansScr $ jansScrTyp ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.18 NAME 'jansPushApp' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( displayName $ jansId $ jansName $ jansPushAppConf ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.19 NAME 'jansPushDevice' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansUsrId $ jansId $ jansPushApp $ jansPushDeviceConf $ jansTyp ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.20 NAME 'jansCustomScr' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( inum $ displayName $ description $ jansScr $ jansScrTyp $ jansProgLng $ jansModuleProperty $ jansConfProperty $ jansLevel $ jansRevision $ jansEnabled $ jansScrError $ jansAlias ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.21 NAME 'jansDeviceRegistration' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansId $ displayName $ description $ jansDeviceKeyHandle $ jansDeviceHashCode $ jansApp $ jansDeviceRegistrationConf $ jansDeviceNotificationConf $ jansNickName $ jansDeviceData $ jansCounter $ jansStatus $ del $ exp $ personInum $ creationDate $ jansLastAccessTime $ jansMetaLastMod $ jansMetaLocation $ jansMetaVer ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.22 NAME 'jansU2fReq' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansId $ jansReqId $ jansReq $ jansSessStateId $ del $ exp $ personInum $ creationDate ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.23 NAME 'jansMetric' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( uniqueIdentifier $ jansStartDate $ jansEndDate $ jansAppTyp $ jansMetricTyp $ creationDate $ del $ exp $ jansData $ jansHost ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.24 NAME 'jansClntAuthz' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansId $ jansClntId $ jansUsrId $ exp $ del $ jansScope ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.25 NAME 'jansSectorIdentifier' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansId $ description $ jansRedirectURI $ jansClntId ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.26 NAME 'jansUmaPCT' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( clnId $ iat $ exp $ del $ tknCde $ jansClaimValues ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.27 NAME 'jansCache' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( uuid $ iat $ exp $ del $ dat ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.28 NAME 'jansFido2AuthnEntry' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansId $ creationDate $ jansSessStateId $ jansCodeChallenge $ personInum $ jansAuthData $ jansStatus ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.29 NAME 'jansFido2RegistrationEntry' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansId $ creationDate $ displayName $ jansSessStateId $ jansCodeChallenge $ jansCodeChallengeHash $ jansPublicKeyId $ personInum $ jansRegistrationData $ jansDeviceNotificationConf $ jansCounter $ jansStatus ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.30 NAME 'jansExpiredObj' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansId $ dat $ iat $ exp $ jansTyp ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.31 NAME 'jansRp' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansId $ dat ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.32 NAME 'jansCibaReq' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( authReqId $ clnId $ usrId $ creationDate $ exp $ jansStatus ) + X-ORIGIN 'Jans created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.33 NAME 'jansStatEntry' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansId $ dat $ attr ) + X-ORIGIN 'Gluu created objectclass' ) +{{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/user-custom-envs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/user-custom-envs.yaml new file mode 100644 index 00000000000..1f08348fec4 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/templates/user-custom-envs.yaml @@ -0,0 +1,66 @@ +{{ if .Values.global.usrEnvs.secret }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-global-user-custom-envs + labels: + APP_NAME: configurator +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.global.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} +{{ if .Values.global.usrEnvs.normal }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-global-user-custom-envs + labels: + APP_NAME: configurator +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +data: + {{- range $key, $val := .Values.global.usrEnvs.normal }} + {{ $key }}: {{ $val }} + {{- end}} +{{- end}} +{{ if .Values.usrEnvs.secret }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs + labels: + APP_NAME: configurator +{{ include "config.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} diff --git a/charts/jans/charts/config/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/values.yaml similarity index 75% rename from charts/jans/charts/config/values.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/config/values.yaml index 3eddfc392fd..421c69a3584 100644 --- a/charts/jans/charts/config/values.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/config/values.yaml @@ -1,6 +1,6 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 -# Required environment variables for generating Janssen server initial config +# Required environment variables for generating Gluu server initial config # -- Add custom normal and secret envs to the service. usrEnvs: # -- Add custom normal envs to the service. @@ -34,6 +34,8 @@ configmap: cnSqldbUserPassword: Test1234# # -- Cache type. `NATIVE_PERSISTENCE`, `REDIS`. or `IN_MEMORY`. Defaults to `NATIVE_PERSISTENCE` . cnCacheType: NATIVE_PERSISTENCE + # -- Enable Casa flag . + cnCasaEnabled: false # -- Client-api OAuth client admin certificate common name. This should be left to the default value client-api . cnClientApiAdminCertCn: client-api # -- Client-api OAuth client application certificate common name. This should be left to the default value client-api. @@ -43,7 +45,7 @@ configmap: containerMetadataName: kubernetes # -- The name of the Kubernetes ConfigMap that will hold the configuration layer cnConfigKubernetesConfigMap: cn - # -- The prefix of couchbase buckets. This helps with separation in between different environments and allows for the same couchbase cluster to be used by different setups of Janssen. + # -- The prefix of couchbase buckets. This helps with separation in between different environments and allows for the same couchbase cluster to be used by different setups of Gluu. cnCouchbaseBucketPrefix: jans # -- Location of `couchbase.crt` used by Couchbase SDK for tls termination. The file path must end with couchbase.crt. In mTLS setups this is not required. cnCouchbaseCertFile: /etc/certs/couchbase.crt @@ -54,19 +56,39 @@ configmap: # -- Couchbase password for the restricted user config.configmap.cnCouchbaseUser that is often used inside the services. The password must contain one digit, one uppercase letter, one lower case letter and one symbol . cnCouchbasePassword: P@ssw0rd # -- The location of the Couchbase restricted user config.configmap.cnCouchbaseUser password. The file path must end with couchbase_password - cnCouchbasePasswordFile: /etc/jans/conf/couchbase_password + cnCouchbasePasswordFile: /etc/gluu/conf/couchbase_password # -- The Couchbase super user (admin) user name. This user is used during initialization only. cnCouchbaseSuperUser: admin # -- Couchbase password for the super user config.configmap.cnCouchbaseSuperUser that is used during the initialization process. The password must contain one digit, one uppercase letter, one lower case letter and one symbol cnCouchbaseSuperUserPassword: Test1234# # -- The location of the Couchbase restricted user config.configmap.cnCouchbaseSuperUser password. The file path must end with couchbase_superuser_password. - cnCouchbaseSuperUserPasswordFile: /etc/jans/conf/couchbase_superuser_password + cnCouchbaseSuperUserPasswordFile: /etc/gluu/conf/couchbase_superuser_password # -- Couchbase URL. Used only when global.cnPersistenceType is hybrid or couchbase. This should be in FQDN format for either remote or local Couchbase clusters. The address can be an internal address inside the kubernetes cluster - cnCouchbaseUrl: cbjans.default.svc.cluster.local + cnCouchbaseUrl: cbgluu.default.svc.cluster.local # -- Couchbase restricted user. Used only when global.cnPersistenceType is hybrid or couchbase. - cnCouchbaseUser: jans + cnCouchbaseUser: gluu # -- Document store type to use for shibboleth files JCA or LOCAL. Note that if JCA is selected Apache Jackrabbit will be used. Jackrabbit also enables loading custom files across all services easily. - cnDocumentStoreType: LOCAL + cnDocumentStoreType: JCA + # -- Jackrabbit admin uid. + cnJackrabbitAdminId: admin + # -- The location of the Jackrabbit admin uid config.cnJackrabbitAdminId. The file path must end with jackrabbit_admin_id. + cnJackrabbitAdminIdFile: /etc/gluu/conf/jackrabbit_admin_id + # -- The location of the Jackrabbit admin password jackrabbit.secrets.cnJackrabbitAdminPassword. The file path must end with jackrabbit_admin_password. + cnJackrabbitAdminPasswordFile: /etc/gluu/conf/jackrabbit_admin_password + # -- Jackrabbit postgres database name. + cnJackrabbitPostgresDatabaseName: jackrabbit + # -- Postgres url + cnJackrabbitPostgresHost: postgresql.postgres.svc.cluster.local + # -- The location of the Jackrabbit postgres password file jackrabbit.secrets.cnJackrabbitPostgresPassword. The file path must end with postgres_password. + cnJackrabbitPostgresPasswordFile: /etc/gluu/conf/postgres_password + # -- Jackrabbit Postgres port + cnJackrabbitPostgresPort: 5432 + # -- Jackrabbit Postgres uid + cnJackrabbitPostgresUser: jackrabbit + # -- Interval between files sync (default to 300 seconds). + cnJackrabbitSyncInterval: 300 + # -- Jackrabbit internal url. Normally left as default. + cnJackrabbitUrl: "http://jackrabbit:8080" # [google_envs] Envs related to using Google # -- Service account with roles roles/secretmanager.admin base64 encoded string. This is used often inside the services to reach the configuration layer. Used only when global.configAdapterName and global.configSecretAdapter is set to google. cnGoogleSecretManagerServiceAccount: SWFtTm90YVNlcnZpY2VBY2NvdW50Q2hhbmdlTWV0b09uZQo= @@ -81,14 +103,14 @@ configmap: # [google_secret_manager_envs] Envs related to using Google Secret Manager to store config and secret layer # -- Secret version to be used for secret configuration. Defaults to latest and should normally always stay that way. Used only when global.configAdapterName and global.configSecretAdapter is set to google. cnSecretGoogleSecretVersionId: "latest" - # -- Prefix for Janssen secret in Google Secret Manager. Defaults to jans. If left jans-secret secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google. - cnSecretGoogleSecretNamePrefix: jans - # -- Passphrase for Janssen secret in Google Secret Manager. This is used for encrypting and decrypting data from the Google Secret Manager. Used only when global.configAdapterName and global.configSecretAdapter is set to google. + # -- Prefix for Gluu secret in Google Secret Manager. Defaults to gluu. If left gluu-secret secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google. + cnSecretGoogleSecretNamePrefix: gluu + # -- Passphrase for Gluu secret in Google Secret Manager. This is used for encrypting and decrypting data from the Google Secret Manager. Used only when global.configAdapterName and global.configSecretAdapter is set to google. cnGoogleSecretManagerPassPhrase: Test1234# # -- Secret version to be used for configuration. Defaults to latest and should normally always stay that way. Used only when global.configAdapterName and global.configSecretAdapter is set to google. Used only when global.configAdapterName and global.configSecretAdapter is set to google. cnConfigGoogleSecretVersionId: "latest" - # -- Prefix for Janssen configuration secret in Google Secret Manager. Defaults to jans. If left intact jans-configuration secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google. - cnConfigGoogleSecretNamePrefix: jans + # -- Prefix for Gluu configuration secret in Google Secret Manager. Defaults to gluu. If left intact gluu-configuration secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google. + cnConfigGoogleSecretNamePrefix: gluu # [google_secret_manager_envs] END # [google_envs] END # -- OpenDJ internal address. Leave as default. Used when `global.cnPersistenceType` is set to `ldap`. @@ -96,6 +118,7 @@ configmap: # -- Value passed to Java option -XX:MaxRAMPercentage cnMaxRamPercent: "75.0" # -- Boolean flag to enable/disable passport chart + cnPassportEnabled: false # -- Specify data that should be saved in LDAP (one of default, user, cache, site, token, or session; default to default). Note this environment only takes effect when `global.cnPersistenceType` is set to `hybrid`. cnPersistenceLdapMapping: default # -- Redis Sentinel Group. Often set when `config.configmap.cnRedisType` is set to `SENTINEL`. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. @@ -117,18 +140,18 @@ configmap: # -- Country code. Used for certificate creation. countryCode: US # -- Email address of the administrator usually. Used for certificate creation. -email: support@jans.io +email: support@gluu.org image: # -- Image to use for deploying. - repository: janssenproject/configuration-manager + repository: janssenproject/configurator # -- Image tag to use for deploying. - tag: 1.0.0_b11 + tag: 1.0.0-beta.14 # -- Image Pull Secrets pullSecrets: [ ] # -- LDAP admin password if OpennDJ is used for persistence. ldapPassword: P@ssw0rds # -- Organization name. Used for certificate creation. -orgName: Janssen +orgName: Gluu # -- Redis admin password if `config.configmap.cnCacheType` is set to `REDIS`. redisPassword: P@assw0rd # -- Resource specs. @@ -163,6 +186,12 @@ migration: # Supported data formats are ldif, couchbase+json, spanner+avro, postgresql+json, and mysql+json. migrationDataFormat: ldif +cnOxtrustConfigGeneration: true nameOverride: "" fullNameOverride: "" + +# -- Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken +additionalAnnotations: { } diff --git a/charts/jans/charts/nginx-ingress/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/.helmignore similarity index 100% rename from charts/jans/charts/nginx-ingress/.helmignore rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/.helmignore diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/Chart.yaml new file mode 100644 index 00000000000..f327b2eb908 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/Chart.yaml @@ -0,0 +1,21 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v2 +name: cr-rotate +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" +description: CacheRefreshRotation is a special container to monitor cache refresh on oxTrust containers. This may become depreciated in 5.0. +type: application +keywords: + - CacheRefresh +home: https://gluu.org/docs/gluu-server +sources: + - https://gluu.org/docs/gluu-server/ + - https://github.com/GluuFederation/docker-cr-rotate + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate +maintainers: + - name: Mohammad Abudayyeh + email: support@gluu.org + url: https://github.com/moabu +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: "5.0.0" diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/README.md new file mode 100644 index 00000000000..41b70dceb58 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/README.md @@ -0,0 +1,55 @@ +# cr-rotate + +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) + +CacheRefreshRotation is a special container to monitor cache refresh on oxTrust containers. This may become depreciated in 5.0. + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | + +## Source Code + +* +* +* + +## Requirements + +Kubernetes: `>=v1.21.0-0` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken | +| additionalLabels | object | `{}` | Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} | +| dnsConfig | object | `{}` | Add custom dns config | +| dnsPolicy | string | `""` | Add custom dns policy | +| fullnameOverride | string | `""` | | +| image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| image.pullSecrets | list | `[]` | Image Pull Secrets | +| image.repository | string | `"gluufederation/cr-rotate"` | Image to use for deploying. | +| image.tag | string | `"5.0.0_dev"` | Image tag to use for deploying. | +| nameOverride | string | `""` | | +| resources | object | `{"limits":{"cpu":"200m","memory":"200Mi"},"requests":{"cpu":"200m","memory":"200Mi"}}` | Resource specs. | +| resources.limits.cpu | string | `"200m"` | CPU limit. | +| resources.limits.memory | string | `"200Mi"` | Memory limit. | +| resources.requests.cpu | string | `"200m"` | CPU request. | +| resources.requests.memory | string | `"200Mi"` | Memory request. | +| service.crRotateServiceName | string | `"cr-rotate"` | Name of the cr-rotate service. Please keep it as default. | +| service.name | string | `"http-cr-rotate"` | The name of the cr-rotate port within the cr-rotate service. Please keep it as default. | +| service.port | int | `8084` | Port of the casa service. Please keep it as default. | +| service.sessionAffinity | string | `"None"` | Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP | +| service.sessionAffinityConfig | object | `{"clientIP":{"timeoutSeconds":10800}}` | the maximum session sticky time if sessionAffinity is ClientIP | +| usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0) diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/_helpers.tpl new file mode 100644 index 00000000000..c8570f6e7cc --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/_helpers.tpl @@ -0,0 +1,69 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "cr-rotate.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "cr-rotate.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "cr-rotate.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* + Common labels +*/}} +{{- define "cr-rotate.labels" -}} +app: {{ .Release.Name }}-{{ include "cr-rotate.name" . }} +helm.sh/chart: {{ include "cr-rotate.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Create user custom defined envs +*/}} +{{- define "cr-rotate.usr-envs"}} +{{- range $key, $val := .Values.usrEnvs.normal }} +- name: {{ $key }} + value: {{ $val }} +{{- end }} +{{- end }} + +{{/* +Create user custom defined secret envs +*/}} +{{- define "cr-rotate.usr-secret-envs"}} +{{- range $key, $val := .Values.usrEnvs.secret }} +- name: {{ $key }} + valueFrom: + secretKeyRef: + name: {{ $.Release.Name }}-{{ $.Chart.Name }}-user-custom-envs + key: {{ $key }} +{{- end }} +{{- end }} + diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/daemonset.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/daemonset.yaml new file mode 100644 index 00000000000..29aaf060175 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/daemonset.yaml @@ -0,0 +1,83 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: {{ include "cr-rotate.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: cr-rotote +{{ include "cr-rotate.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + selector: + matchLabels: + app: {{ .Release.Name }}-{{ include "cr-rotate.name" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ include "cr-rotate.name" . }} + release: {{ .Release.Name }} + APP_NAME: cr-rotate + spec: + {{- with .Values.image.pullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + dnsPolicy: {{ .Values.dnsPolicy | quote }} + {{- with .Values.dnsConfig }} + dnsConfig: +{{ toYaml . | indent 8 }} + {{- end }} + containers: + - name: {{ include "cr-rotate.name" . }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + env: + {{- include "cr-rotate.usr-envs" . | indent 12 }} + {{- include "cr-rotate.usr-secret-envs" . | indent 12 }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + envFrom: + - configMapRef: + name: {{ .Release.Name }}-config-cm + {{ if .Values.global.usrEnvs.secret }} + - secretRef: + name: {{ .Release.Name }}-global-user-custom-envs + {{- end }} + {{ if .Values.global.usrEnvs.normal }} + - configMapRef: + name: {{ .Release.Name }}-global-user-custom-envs + {{- end }} + volumeMounts: + {{- with .Values.volumeMounts }} +{{- toYaml . | nindent 12 }} + {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} + - name: cb-crt + mountPath: "/etc/certs/couchbase.crt" + subPath: couchbase.crt + {{- end }} + {{- if or (eq .Values.global.storageClass.provisioner "microk8s.io/hostpath" ) (eq .Values.global.storageClass.provisioner "k8s.io/minikube-hostpath") }} + resources: {} + {{- else if .Values.global.cloud.testEnviroment }} + resources: {} + {{- else }} + resources: +{{- toYaml .Values.resources | nindent 12 }} + {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} + volumes: + {{- with .Values.volumes }} +{{- toYaml . | nindent 8 }} + {{- end }} + + - name: cb-crt + secret: + secretName: {{ .Release.Name }}-cb-crt + {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/service.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/service.yaml new file mode 100644 index 00000000000..404a15ec80c --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/service.yaml @@ -0,0 +1,34 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.service.crRotateServiceName }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Release.Name }}-{{ include "cr-rotate.name" . }} + chart: {{ include "cr-rotate.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + APP_NAME: cr-rotote +{{ include "cr-rotate.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + ports: + - port: {{ .Values.service.port }} + protocol: TCP + name: {{ .Values.service.name }} + selector: + app: {{ .Release.Name }}-{{ include "cr-rotate.name" . }} + release: {{ .Release.Name }} + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- with .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: +{{ toYaml . | indent 4 }} + {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/user-custom-secret-envs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/user-custom-secret-envs.yaml new file mode 100644 index 00000000000..ec8a84a1e95 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/templates/user-custom-secret-envs.yaml @@ -0,0 +1,23 @@ +{{ if .Values.usrEnvs.secret }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs + labels: + APP_NAME: cr-rotote +{{ include "cr-rotate.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/values.yaml new file mode 100644 index 00000000000..14c69e3b14b --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/cr-rotate/values.yaml @@ -0,0 +1,61 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +# -- CacheRefreshRotation is a special container to monitor cache refresh on oxTrust containers. This may be depreciated. +# -- Add custom normal and secret envs to the service +usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} +# -- Add custom dns policy +dnsPolicy: "" +# -- Add custom dns config +dnsConfig: {} +image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: gluufederation/cr-rotate + # -- Image tag to use for deploying. + tag: 5.0.0_dev + # -- Image Pull Secrets + pullSecrets: [ ] +# -- Resource specs. +resources: + limits: + # -- CPU limit. + cpu: 200m + # -- Memory limit. + memory: 200Mi + requests: + # -- CPU request. + cpu: 200m + # -- Memory request. + memory: 200Mi +service: + # -- Name of the cr-rotate service. Please keep it as default. + crRotateServiceName: cr-rotate + # -- Port of the casa service. Please keep it as default. + port: 8084 + # -- The name of the cr-rotate port within the cr-rotate service. Please keep it as default. + name: http-cr-rotate + # -- Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP + sessionAffinity: None + # -- the maximum session sticky time if sessionAffinity is ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: 10800 +# -- Configure any additional volumes that need to be attached to the pod +volumes: [] +# -- Configure any additional volumesMounts that need to be attached to the containers +volumeMounts: [] + +nameOverride: "" +fullnameOverride: "" + +# -- Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken +additionalAnnotations: { } \ No newline at end of file diff --git a/charts/jans/charts/opendj/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/.helmignore similarity index 100% rename from charts/jans/charts/opendj/.helmignore rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/.helmignore diff --git a/charts/jans/charts/fido2/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/Chart.yaml similarity index 54% rename from charts/jans/charts/fido2/Chart.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/Chart.yaml index ce802658062..1c838f9d8b6 100644 --- a/charts/jans/charts/fido2/Chart.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/Chart.yaml @@ -1,23 +1,23 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: v2 name: fido2 -version: 1.0.0-b11 -kubeVersion: ">=v1.19.0-0" +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" description: FIDO 2.0 (FIDO2) is an open authentication standard that enables leveraging common devices to authenticate to online services in both mobile and desktop environments. type: application keywords: - fido2 - u2f -home: https://jans.io/ +home: https://gluu.org/docs/gluu-server/ sources: - - https://jans.io/ + - https://gluu.org/docs/gluu-server/ - https://github.com/JanssenProject/jans-fido2 - https://github.com/JanssenProject/docker-jans-fido2 - - https://github.com/JanssenFederation/cloud-native-edition/tree/master/pyjans/kubernetes/templates/helm/jans/charts/fido2 + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/fido2 maintainers: - name: Mohammad Abudayyeh - email: support@jans.io + email: support@gluu.org url: https://github.com/moabu -icon: https://jans.io/favicon.ico -appVersion: "1.0.0-b11" +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: "5.0.0" diff --git a/charts/jans/charts/fido2/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/README.md similarity index 67% rename from charts/jans/charts/fido2/README.md rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/README.md index f739778d02e..79ef3c0400c 100644 --- a/charts/jans/charts/fido2/README.md +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/README.md @@ -1,32 +1,34 @@ # fido2 -![Version: 1.0.0-b11](https://img.shields.io/badge/Version-1.0.0--b11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-b11](https://img.shields.io/badge/AppVersion-1.0.0--b11-informational?style=flat-square) +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) FIDO 2.0 (FIDO2) is an open authentication standard that enables leveraging common devices to authenticate to online services in both mobile and desktop environments. -**Homepage:** +**Homepage:** ## Maintainers | Name | Email | Url | | ---- | ------ | --- | -| Mohammad Abudayyeh | support@jans.io | https://github.com/moabu | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | ## Source Code -* +* * * -* +* ## Requirements -Kubernetes: `>=v1.19.0-0` +Kubernetes: `>=v1.21.0-0` ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken | +| additionalLabels | object | `{}` | Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} | | dnsConfig | object | `{}` | Add custom dns config | | dnsPolicy | string | `""` | Add custom dns policy | | hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | @@ -35,7 +37,7 @@ Kubernetes: `>=v1.19.0-0` | image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | | image.pullSecrets | list | `[]` | Image Pull Secrets | | image.repository | string | `"janssenproject/fido2"` | Image to use for deploying. | -| image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | +| image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | | livenessProbe | object | `{"httpGet":{"path":"/jans-fido2/sys/health-check","port":"http-fido2"},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the liveness healthcheck for the fido2 if needed. | | livenessProbe.httpGet | object | `{"path":"/jans-fido2/sys/health-check","port":"http-fido2"}` | http liveness probe endpoint | | readinessProbe | object | `{"httpGet":{"path":"/jans-fido2/sys/health-check","port":"http-fido2"},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the readiness healthcheck for the fido2 if needed. | @@ -47,6 +49,8 @@ Kubernetes: `>=v1.19.0-0` | resources.requests.memory | string | `"500Mi"` | Memory request. | | service.name | string | `"http-fido2"` | The name of the fido2 port within the fido2 service. Please keep it as default. | | service.port | int | `8080` | Port of the fido2 service. Please keep it as default. | +| service.sessionAffinity | string | `"None"` | Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP | +| service.sessionAffinityConfig | object | `{"clientIP":{"timeoutSeconds":10800}}` | the maximum session sticky time if sessionAffinity is ClientIP | | usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | | usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | | usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | diff --git a/charts/jans/charts/fido2/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/_helpers.tpl similarity index 100% rename from charts/jans/charts/fido2/templates/_helpers.tpl rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/_helpers.tpl diff --git a/charts/jans/charts/fido2/templates/deployment.yml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/deployment.yml similarity index 88% rename from charts/jans/charts/fido2/templates/deployment.yml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/deployment.yml index 94c013a4c3b..e19f35299af 100644 --- a/charts/jans/charts/fido2/templates/deployment.yml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/deployment.yml @@ -1,4 +1,4 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: apps/v1 kind: Deployment @@ -8,6 +8,13 @@ metadata: labels: APP_NAME: fido2 {{ include "fido2.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: replicas: {{ .Values.replicas }} selector: @@ -73,19 +80,13 @@ spec: name: google-sa subPath: google-credentials.json {{- end }} - {{- if eq .Values.global.cnPersistenceType "sql" }} - - name: sql-pass - mountPath: "/etc/jans/conf/sql_password" - subPath: sql_password - {{- end }} + {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} - name: {{ include "fido2.fullname" .}}-updatelbip mountPath: "/scripts" {{- end }} {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} - - name: cb-pass - mountPath: "/etc/jans/conf/couchbase_password" - subPath: couchbase_password + {{- if not .Values.global.istio.enabled }} - name: cb-crt mountPath: "/etc/certs/couchbase.crt" @@ -119,15 +120,9 @@ spec: secret: secretName: {{ .Release.Name }}-google-sa {{- end }} - {{- if eq .Values.global.cnPersistenceType "sql" }} - - name: sql-pass - secret: - secretName: {{ .Release.Name }}-sql-pass - {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} - - name: cb-pass - secret: - secretName: {{ .Release.Name }}-cb-pass + {{- if not .Values.global.istio.enabled }} - name: cb-crt secret: diff --git a/charts/jans/charts/fido2/templates/fido2-destination-rules.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/fido2-destination-rules.yaml similarity index 51% rename from charts/jans/charts/fido2/templates/fido2-destination-rules.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/fido2-destination-rules.yaml index cedcd8b210a..84221c9ba64 100644 --- a/charts/jans/charts/fido2/templates/fido2-destination-rules.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/fido2-destination-rules.yaml @@ -1,11 +1,21 @@ {{- if .Values.global.istio.enabled }} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: {{ .Release.Name }}-fido2-mtls namespace: {{.Release.Namespace}} + labels: + APP_NAME: fido2 +{{ include "fido2.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: host: {{ .Values.global.fido2.fido2ServiceName }}.{{ .Release.Namespace }}.svc.cluster.local trafficPolicy: diff --git a/charts/jans/charts/fido2/templates/fido2-virtual-services.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/fido2-virtual-services.yaml similarity index 67% rename from charts/jans/charts/fido2/templates/fido2-virtual-services.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/fido2-virtual-services.yaml index 145a8fe1d1a..af0721a8b4d 100644 --- a/charts/jans/charts/fido2/templates/fido2-virtual-services.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/fido2-virtual-services.yaml @@ -1,11 +1,21 @@ {{- if .Values.global.istio.ingress }} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: {{ .Release.Name }}-istio-fido2-configuration namespace: {{.Release.Namespace}} + labels: + APP_NAME: fido2 +{{ include "fido2.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: hosts: - {{ .Values.global.fqdn }} diff --git a/charts/jans/charts/fido2/templates/hpa.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/hpa.yaml similarity index 68% rename from charts/jans/charts/fido2/templates/hpa.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/hpa.yaml index 739fd74400f..1f0aeb8c2e1 100644 --- a/charts/jans/charts/fido2/templates/hpa.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/hpa.yaml @@ -1,10 +1,20 @@ {{ if .Values.hpa.enabled -}} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: {{ include "fido2.fullname" . }} + labels: + APP_NAME: fido2 +{{ include "fido2.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: scaleTargetRef: apiVersion: apps/v1 diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/service.yml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/service.yml new file mode 100644 index 00000000000..694a3407c07 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/service.yml @@ -0,0 +1,31 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.global.fido2.fido2ServiceName }} + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: fido2 +{{ include "fido2.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + {{- if .Values.global.alb.ingress }} + type: NodePort + {{- end }} + ports: + - port: {{ .Values.service.port }} + name: {{ .Values.service.name }} + selector: + app: {{ .Release.Name }}-{{ include "fido2.name" . }} #fido2 + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- with .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: +{{ toYaml . | indent 4 }} + {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/user-custom-secret-envs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/user-custom-secret-envs.yaml new file mode 100644 index 00000000000..fb0afcfa90b --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/templates/user-custom-secret-envs.yaml @@ -0,0 +1,23 @@ +{{ if .Values.usrEnvs.secret }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs + labels: + APP_NAME: fido2 +{{ include "fido2.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} \ No newline at end of file diff --git a/charts/jans/charts/fido2/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/values.yaml similarity index 72% rename from charts/jans/charts/fido2/values.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/values.yaml index c1bf962a93b..12fdbc4f0ca 100644 --- a/charts/jans/charts/fido2/values.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/fido2/values.yaml @@ -1,4 +1,4 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 # -- FIDO 2.0 (FIDO2) is an open authentication standard that enables leveraging common devices to authenticate to online services in both mobile and desktop environments. @@ -30,7 +30,7 @@ image: # -- Image to use for deploying. repository: janssenproject/fido2 # -- Image tag to use for deploying. - tag: 1.0.0_b11 + tag: 1.0.0-beta.14 # -- Image Pull Secrets pullSecrets: [ ] # -- Service replica number. @@ -52,6 +52,12 @@ service: name: http-fido2 # -- Port of the fido2 service. Please keep it as default. port: 8080 + # -- Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP + sessionAffinity: None + # -- the maximum session sticky time if sessionAffinity is ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: 10800 # -- Configure the liveness healthcheck for the fido2 if needed. livenessProbe: # -- http liveness probe endpoint @@ -72,4 +78,9 @@ readinessProbe: # -- Configure any additional volumes that need to be attached to the pod volumes: [] # -- Configure any additional volumesMounts that need to be attached to the containers -volumeMounts: [] \ No newline at end of file +volumeMounts: [] + +# -- Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken +additionalAnnotations: { } diff --git a/charts/jans/charts/scim/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/.helmignore similarity index 100% rename from charts/jans/charts/scim/.helmignore rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/.helmignore diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/Chart.yaml new file mode 100644 index 00000000000..f5b4fe57123 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/Chart.yaml @@ -0,0 +1,23 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v2 +name: jackrabbit +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" +description: Jackrabbit Oak is a complementary implementation of the JCR specification. It is an effort to implement a scalable and performant hierarchical content repository for use as the foundation of modern world-class web sites and other demanding content applications. +type: application +keywords: + - jackrabbit + - content repository +home: https://gluu.org/docs/gluu-server/installation-guide/install-kubernetes/#working-with-jackrabbit +sources: + - https://gluu.org/docs/gluu-server/installation-guide/install-kubernetes/#working-with-jackrabbit + - https://github.com/GluuFederation/docker-jackrabbit + - https://jackrabbit.apache.org/jcr/index.html + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit +maintainers: + - name: Mohammad Abudayyeh + email: support@gluu.org + url: https://github.com/moabu +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: "5.0.0" diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/README.md new file mode 100644 index 00000000000..d1fd6542279 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/README.md @@ -0,0 +1,79 @@ +# jackrabbit + +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) + +Jackrabbit Oak is a complementary implementation of the JCR specification. It is an effort to implement a scalable and performant hierarchical content repository for use as the foundation of modern world-class web sites and other demanding content applications. + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | + +## Source Code + +* +* +* +* + +## Requirements + +Kubernetes: `>=v1.21.0-0` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} | +| additionalLabels | object | `{}` | Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} | +| clusterId | string | `""` | This id needs to be unique to each kubernetes cluster in a multi cluster setup west, east, south, north, region ...etc If left empty it will be randomly generated. | +| dnsConfig | object | `{}` | Add custom dns config | +| dnsPolicy | string | `""` | Add custom dns policy | +| fullnameOverride | string | `""` | | +| hpa.behavior | object | `{}` | Scaling Policies | +| hpa.enabled | bool | `true` | | +| hpa.maxReplicas | int | `10` | | +| hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| hpa.minReplicas | int | `1` | | +| hpa.targetCPUUtilizationPercentage | int | `50` | | +| image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| image.pullSecrets | list | `[]` | Image Pull Secrets | +| image.repository | string | `"gluufederation/jackrabbit"` | Image to use for deploying. | +| image.tag | string | `"5.0.0_dev"` | Image tag to use for deploying. | +| jackrabbitVolumeMounts.repository.mountPath | string | `"/opt/jackrabbit/repository"` | | +| jackrabbitVolumeMounts.repository.name | string | `"jackrabbit-volume"` | | +| jackrabbitVolumeMounts.version.mountPath | string | `"/opt/jackrabbit/version"` | | +| jackrabbitVolumeMounts.version.name | string | `"jackrabbit-volume"` | | +| jackrabbitVolumeMounts.workspaces.mountPath | string | `"opt/jackrabbit/workspaces"` | | +| jackrabbitVolumeMounts.workspaces.name | string | `"jackrabbit-volume"` | | +| livenessProbe | object | `{"initialDelaySeconds":25,"periodSeconds":25,"tcpSocket":{"port":"http-jackrabbit"},"timeoutSeconds":5}` | Configure the liveness healthcheck for the Jackrabbit if needed. | +| livenessProbe.tcpSocket | object | `{"port":"http-jackrabbit"}` | Executes tcp healthcheck. | +| nameOverride | string | `""` | | +| readinessProbe | object | `{"initialDelaySeconds":30,"periodSeconds":30,"tcpSocket":{"port":"http-jackrabbit"},"timeoutSeconds":5}` | Configure the readiness healthcheck for the Jackrabbit if needed. | +| readinessProbe.tcpSocket | object | `{"port":"http-jackrabbit"}` | Executes tcp healthcheck. | +| replicas | int | `1` | Service replica number. | +| resources | object | `{"limits":{"cpu":"1500m","memory":"1000Mi"},"requests":{"cpu":"1500m","memory":"1000Mi"}}` | Resource specs. | +| resources.limits.cpu | string | `"1500m"` | CPU limit. | +| resources.limits.memory | string | `"1000Mi"` | Memory limit. | +| resources.requests.cpu | string | `"1500m"` | CPU request. | +| resources.requests.memory | string | `"1000Mi"` | Memory request. | +| secrets.cnJackrabbitAdminPassword | string | `"admin"` | Jackrabbit admin uid password | +| secrets.cnJackrabbitPostgresPassword | string | `"P@ssw0rd"` | Jackrabbit Postgres uid password | +| service.name | string | `"http-jackrabbit"` | The name of the jackrabbit port within the jackrabbit service. Please keep it as default. | +| service.port | int | `8080` | Port of the jackrabbit service. Please keep it as default. | +| service.sessionAffinity | string | `"None"` | Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP | +| service.sessionAffinityConfig | object | `{"clientIP":{"timeoutSeconds":10800}}` | the maximum session sticky time if sessionAffinity is ClientIP | +| storage.accessModes | string | `"ReadWriteOnce"` | | +| storage.size | string | `"5Gi"` | Jackrabbit volume size | +| storage.type | string | `"DirectoryOrCreate"` | | +| usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0) diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/_helpers.tpl new file mode 100644 index 00000000000..1ff58881793 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/_helpers.tpl @@ -0,0 +1,83 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "jackrabbit.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "jackrabbit.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Generate random clusterId to appended to the name. This is relevent expecially when there are multiple kubernetes clusters where this id otherwise would be the same. +In Jackrabbit: + + + + +*/}} +{{- define "jackrabbit.clusterId" -}} +{{- if .Values.clusterId -}} +{{- .Values.clusterId | lower -}} +{{- else -}} +{{- randAlpha 5 | lower -}} +{{- end -}} +{{- end -}} +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "jackrabbit.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* + Common labels +*/}} +{{- define "jackrabbit.labels" -}} +app: {{ .Release.Name }}-{{ include "jackrabbit.name" . }} +helm.sh/chart: {{ include "jackrabbit.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Create user custom defined envs +*/}} +{{- define "jackrabbit.usr-envs"}} +{{- range $key, $val := .Values.usrEnvs.normal }} +- name: {{ $key }} + value: {{ $val }} +{{- end }} +{{- end }} + +{{/* +Create user custom defined secret envs +*/}} +{{- define "jackrabbit.usr-secret-envs"}} +{{- range $key, $val := .Values.usrEnvs.secret }} +- name: {{ $key }} + valueFrom: + secretKeyRef: + name: {{ $.Release.Name }}-{{ $.Chart.Name }}-user-custom-envs + key: {{ $key }} +{{- end }} +{{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/hpa.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/hpa.yaml new file mode 100644 index 00000000000..c1b1e022aa8 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/hpa.yaml @@ -0,0 +1,38 @@ +{{ if .Values.hpa.enabled -}} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: autoscaling/v1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "jackrabbit.fullname" . }}-{{ include "jackrabbit.clusterId" . }} + labels: +{{ include "jackrabbit.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: StatefulSet + name: {{ include "jackrabbit.fullname" . }}-{{ include "jackrabbit.clusterId" . }} + minReplicas: {{ .Values.hpa.minReplicas }} + maxReplicas: {{ .Values.hpa.maxReplicas }} + {{- if .Values.hpa.targetCPUUtilizationPercentage }} + targetCPUUtilizationPercentage: {{ .Values.hpa.targetCPUUtilizationPercentage }} + {{- else if .Values.hpa.metrics }} + metrics: + {{- with .Values.hpa.metrics }} +{{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} + {{- if .Values.hpa.behavior }} + behavior: + {{- with .Values.hpa.behavior }} +{{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/jackrabbit-destination-rules.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/jackrabbit-destination-rules.yaml new file mode 100644 index 00000000000..c5b384bb301 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/jackrabbit-destination-rules.yaml @@ -0,0 +1,23 @@ +{{- if .Values.global.istio.enabled }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: {{ .Release.Name }}-jackrabbit-mtls + namespace: {{.Release.Namespace}} + labels: +{{ include "jackrabbit.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + host: {{ .Values.global.jackrabbit.jackRabbitServiceName }}.{{ .Release.Namespace }}.svc.cluster.local + trafficPolicy: + tls: + mode: ISTIO_MUTUAL +{{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/secret.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/secret.yaml new file mode 100644 index 00000000000..c93bfe7cd6e --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/secret.yaml @@ -0,0 +1,37 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: cn-jackrabbit-admin-pass + namespace: {{ .Release.Namespace }} + labels: +{{ include "jackrabbit.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +data: + jackrabbit_admin_password: {{ .Values.secrets.cnJackrabbitAdminPassword | b64enc }} +{{ if .Values.global.cnJackrabbitCluster }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: cn-jackrabbit-postgres-pass + namespace: {{ .Release.Namespace }} + labels: +{{ include "jackrabbit.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +data: + postgres_password: {{ .Values.secrets.cnJackrabbitPostgresPassword | b64enc }} +{{- end -}} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/service.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/service.yaml new file mode 100644 index 00000000000..c0a57207937 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/service.yaml @@ -0,0 +1,28 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.global.jackrabbit.jackRabbitServiceName }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "jackrabbit.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + ports: + - port: {{ .Values.service.port }} + name: {{ .Values.service.name }} + clusterIP: None + selector: + app: {{ .Release.Name }}-{{ include "jackrabbit.name" . }} + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- with .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: +{{ toYaml . | indent 4 }} + {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/statefulset.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/statefulset.yaml new file mode 100644 index 00000000000..f31ab58b5d2 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/statefulset.yaml @@ -0,0 +1,117 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "jackrabbit.fullname" . }}-{{ include "jackrabbit.clusterId" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "jackrabbit.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + selector: + matchLabels: + app: {{ .Release.Name }}-{{ include "jackrabbit.name" . }} + serviceName: {{ include "jackrabbit.name" . }} + replicas: {{ .Values.replicas }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ include "jackrabbit.name" . }} + {{- if .Values.global.istio.ingress }} + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + {{- end }} + spec: + {{- with .Values.image.pullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + dnsPolicy: {{ .Values.dnsPolicy | quote }} + {{- with .Values.dnsConfig }} + dnsConfig: +{{ toYaml . | indent 8 }} + {{- end }} + volumes: + {{- with .Values.volumes }} +{{- toYaml . | nindent 8 }} + {{- end }} + - name: cn-jackrabbit-admin-pass + secret: + secretName: cn-jackrabbit-admin-pass + {{- if .Values.global.cnJackrabbitCluster }} + - name: cn-jackrabbit-postgres-pass + secret: + secretName: cn-jackrabbit-postgres-pass + {{- end }} + containers: + - name: {{ include "jackrabbit.name" . }} + env: + {{- include "jackrabbit.usr-envs" . | indent 12 }} + {{- include "jackrabbit.usr-secret-envs" . | indent 12 }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + envFrom: + - configMapRef: + name: {{ .Release.Name }}-config-cm + {{ if .Values.global.usrEnvs.secret }} + - secretRef: + name: {{ .Release.Name }}-global-user-custom-envs + {{- end }} + {{ if .Values.global.usrEnvs.normal }} + - configMapRef: + name: {{ .Release.Name }}-global-user-custom-envs + {{- end }} + ports: + - name: {{ .Values.service.name }} + containerPort: {{ .Values.service.port }} + protocol: TCP + volumeMounts: + {{- with .Values.volumeMounts }} +{{- toYaml . | nindent 10 }} + {{- end }} + - name: cn-jackrabbit-admin-pass + mountPath: /etc/gluu/conf/jackrabbit_admin_password + subPath: jackrabbit_admin_password + {{- if .Values.global.cnJackrabbitCluster }} + - name: cn-jackrabbit-postgres-pass + mountPath: /etc/gluu/conf/postgres_password + subPath: postgres_password + {{- end }} + {{- range $key, $values := .Values.jackrabbitVolumeMounts }} + - mountPath: {{$values.mountPath}} + name: {{$values.name}} + subPath: {{$key}} + {{- end }} + readinessProbe: +{{- toYaml .Values.readinessProbe | nindent 10 }} + livenessProbe: +{{- toYaml .Values.livenessProbe | nindent 10 }} + {{- if or (eq .Values.global.storageClass.provisioner "microk8s.io/hostpath" ) (eq .Values.global.storageClass.provisioner "k8s.io/minikube-hostpath") }} + resources: {} + {{- else if .Values.global.cloud.testEnviroment }} + resources: {} + {{- else }} + resources: +{{- toYaml .Values.resources | nindent 10 }} + {{- end }} + volumeClaimTemplates: + - metadata: + name: jackrabbit-volume + spec: + accessModes: + - {{ .Values.storage.accessModes }} + resources: + requests: + storage: {{ .Values.storage.size }} + {{- if eq .Values.global.storageClass.provisioner "k8s.io/minikube-hostpath" }} + storageClassName: standard + {{- else }} + storageClassName: {{ include "jackrabbit.fullname" . | quote }} + {{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/storageclass.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/storageclass.yaml new file mode 100644 index 00000000000..ee7281c2fa1 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/storageclass.yaml @@ -0,0 +1,58 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: {{ include "jackrabbit.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + storage: jackrabbit +{{ include "jackrabbit.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} + annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-weight": "2" + "helm.sh/hook-delete-policy": before-hook-creation +{{- if .Values.additionalAnnotations }} +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} + annotations: + # Annotation below is to keep the storage class during upgrade. Otherwise, due to the flag at line 1 which is needed, this resource will be deleted. + helm.sh/resource-policy: keep + storageclass.beta.kubernetes.io/is-default-class: "false" + {{- if eq .Values.global.storageClass.provisioner "openebs.io/local" }} + openebs.io/cas-type: local + cas.openebs.io/config: | + - name: StorageType + value: hostpath + - name: BasePath + value: /var/local-hostpath + {{- end }} +provisioner: {{ .Values.global.storageClass.provisioner }} +{{- if and ( ne .Values.global.storageClass.provisioner "microk8s.io/hostpath" ) ( ne .Values.global.storageClass.provisioner "k8s.io/minikube-hostpath") ( ne .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") ( ne .Values.global.storageClass.provisioner "kubernetes.io/gce-pd") ( ne .Values.global.storageClass.provisioner "dobs.csi.digitalocean.com") ( ne .Values.global.storageClass.provisioner "openebs.io/local") ( ne .Values.global.storageClass.provisioner "kubernetes.io/azure-disk") }} +parameters: +{{ toYaml .Values.global.storageClass.parameters | indent 4 }} +{{- else }} +parameters: + {{- if eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs" }} + type: {{ .Values.global.awsStorageType }} + fsType: ext4 + {{- else if eq .Values.global.storageClass.provisioner "kubernetes.io/gce-pd" }} + type: {{ .Values.global.gcePdStorageType }} + {{- else if eq .Values.global.storageClass.provisioner "kubernetes.io/azure-disk" }} + storageAccountType: {{ .Values.global.azureStorageAccountType }} + kind: {{ .Values.global.azureStorageKind }} + {{- else if eq .Values.global.storageClass.provisioner "dobs.csi.digitalocean.com" }} + {{- else if eq .Values.global.storageClass.provisioner "openebs.io/local" }} + {{- else }} + pool: default + fsType: ext4 + {{- end }} +{{- end }} +allowVolumeExpansion: {{ .Values.global.storageClass.allowVolumeExpansion }} +volumeBindingMode: {{ .Values.global.storageClass.volumeBindingMode }} +reclaimPolicy: {{ .Values.global.storageClass.reclaimPolicy }} +mountOptions: {{ .Values.global.storageClass.mountOptions | toJson }} +allowedTopologies: {{ .Values.global.storageClass.allowedTopologies | toJson }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/user-custom-secret-envs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/user-custom-secret-envs.yaml new file mode 100644 index 00000000000..21d4c18647b --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/templates/user-custom-secret-envs.yaml @@ -0,0 +1,22 @@ +{{ if .Values.usrEnvs.secret }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs + labels: +{{ include "jackrabbit.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/values.yaml new file mode 100644 index 00000000000..da8b96cfc7e --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/jackrabbit/values.yaml @@ -0,0 +1,116 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +# jackrabbit Environament Variables +# -- Jackrabbit Oak is a complementary implementation of the JCR specification. It is an effort to implement a scalable and performant hierarchical content repository for use as the foundation of modern world-class web sites and other demanding content applications +# https://jackrabbit.apache.org/jcr/index.html +# -- Configure the HorizontalPodAutoscaler +hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} +# -- Add custom normal and secret envs to the service +usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} +# -- Add custom dns policy +dnsPolicy: "" +# -- Add custom dns config +dnsConfig: {} +image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: gluufederation/jackrabbit + # -- Image tag to use for deploying. + tag: 5.0.0_dev + # -- Image Pull Secrets + pullSecrets: [ ] +# -- Service replica number. +replicas: 1 +# -- Resource specs. +resources: + limits: + # -- CPU limit. + cpu: 1500m + # -- Memory limit. + memory: 1000Mi + requests: + # -- CPU request. + cpu: 1500m + # -- Memory request. + memory: 1000Mi +secrets: + # -- Jackrabbit admin uid password + cnJackrabbitAdminPassword: admin + # -- Jackrabbit Postgres uid password + cnJackrabbitPostgresPassword: P@ssw0rd +service: + # -- The name of the jackrabbit port within the jackrabbit service. Please keep it as default. + name: http-jackrabbit + # -- Port of the jackrabbit service. Please keep it as default. + port: 8080 + # -- Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP + sessionAffinity: None + # -- the maximum session sticky time if sessionAffinity is ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: 10800 + +storage: + # -- Jackrabbit volume size + size: 5Gi + accessModes: ReadWriteOnce + type: DirectoryOrCreate +# -- Configure the liveness healthcheck for the Jackrabbit if needed. +livenessProbe: + # -- Executes tcp healthcheck. + tcpSocket: + port: http-jackrabbit + initialDelaySeconds: 25 + periodSeconds: 25 + timeoutSeconds: 5 +# -- Configure the readiness healthcheck for the Jackrabbit if needed. +readinessProbe: + # -- Executes tcp healthcheck. + tcpSocket: + port: http-jackrabbit + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 +# -- Configure any additional volumes that need to be attached to the pod +volumes: [] +# -- Configure any additional volumesMounts that need to be attached to the containers +volumeMounts: [] + +nameOverride: "" +fullnameOverride: "" +# -- This id needs to be unique to each kubernetes cluster in a multi cluster setup +# west, east, south, north, region ...etc If left empty it will be randomly generated. +clusterId: "" + +# VolumeMounts for StatefulSet +# jackrabbit-init vm +jackrabbitVolumeMounts: + repository: + mountPath: /opt/jackrabbit/repository + name: jackrabbit-volume + version: + mountPath: /opt/jackrabbit/version + name: jackrabbit-volume + workspaces: + mountPath: opt/jackrabbit/workspaces + name: jackrabbit-volume + +# -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} +additionalAnnotations: { } diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/.helmignore new file mode 100644 index 00000000000..f0c13194444 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/charts/jans/charts/nginx-ingress/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/Chart.yaml similarity index 50% rename from charts/jans/charts/nginx-ingress/Chart.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/Chart.yaml index 12463ec983d..a9e403c5735 100644 --- a/charts/jans/charts/nginx-ingress/Chart.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/Chart.yaml @@ -1,22 +1,22 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: v2 name: nginx-ingress -version: 1.0.0-b11 -kubeVersion: ">=v1.19.0-0" +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" description: Nginx ingress definitions chart type: application keywords: - nginx - ingress -home: https://jans.io +home: https://gluu.org/docs/gluu-server sources: - https://github.com/kubernetes/ingress-nginx - https://kubernetes.io/docs/concepts/services-networking/ingress/ - - https://github.com/JanssenFederation/cloud-native-edition/tree/4.3/pyjans/kubernetes/templates/helm/jans/charts/nginx-ingress + - https://github.com/GluuFederation/cloud-native-edition/tree/4.3/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress maintainers: - name: Mohammad Abudayyeh - email: support@jans.io + email: support@gluu.org url: https://github.com/moabu -icon: https://jans.io/favicon.ico -appVersion: "1.0.0-b11" +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: "5.0.0" diff --git a/charts/jans/charts/nginx-ingress/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/README.md similarity index 83% rename from charts/jans/charts/nginx-ingress/README.md rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/README.md index 6a446271435..509b28be00f 100644 --- a/charts/jans/charts/nginx-ingress/README.md +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/README.md @@ -1,26 +1,26 @@ # nginx-ingress -![Version: 1.0.0-b11](https://img.shields.io/badge/Version-1.0.0--b11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-b11](https://img.shields.io/badge/AppVersion-1.0.0--b11-informational?style=flat-square) +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) Nginx ingress definitions chart -**Homepage:** +**Homepage:** ## Maintainers | Name | Email | Url | | ---- | ------ | --- | -| Mohammad Abudayyeh | support@jans.io | https://github.com/moabu | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | ## Source Code * * -* +* ## Requirements -Kubernetes: `>=v1.19.0-0` +Kubernetes: `>=v1.21.0-0` ## Values @@ -42,7 +42,7 @@ Kubernetes: `>=v1.19.0-0` | ingress.configApiLabels | object | `{}` | configAPI ingress resource labels. key app is taken | | ingress.fido2ConfigEnabled | bool | `false` | Enable endpoint /.well-known/fido2-configuration | | ingress.fido2ConfigLabels | object | `{}` | fido2 config ingress resource labels. key app is taken | -| ingress.hosts[0] | string | `"demoexample.jans.io"` | | +| ingress.hosts[0] | string | `"demoexample.gluu.org"` | | | ingress.openidConfigEnabled | bool | `true` | Enable endpoint /.well-known/openid-configuration | | ingress.openidConfigLabels | object | `{}` | openid-configuration ingress resource labels. key app is taken | | ingress.path | string | `"/"` | | @@ -50,7 +50,7 @@ Kubernetes: `>=v1.19.0-0` | ingress.scimConfigLabels | object | `{}` | webdiscovery ingress resource labels. key app is taken | | ingress.scimEnabled | bool | `false` | Enable SCIM endpoints /jans-scim | | ingress.scimLabels | object | `{}` | scim config ingress resource labels. key app is taken | -| ingress.tls[0].hosts[0] | string | `"demoexample.jans.io"` | | +| ingress.tls[0].hosts[0] | string | `"demoexample.gluu.org"` | | | ingress.tls[0].secretName | string | `"tls-certificate"` | | | ingress.u2fConfigEnabled | bool | `true` | Enable endpoint /.well-known/fido-configuration | | ingress.u2fConfigLabels | object | `{}` | u2f config ingress resource labels. key app is taken | diff --git a/charts/jans/charts/nginx-ingress/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/templates/_helpers.tpl similarity index 100% rename from charts/jans/charts/nginx-ingress/templates/_helpers.tpl rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/templates/_helpers.tpl diff --git a/charts/jans/charts/nginx-ingress/templates/ingress.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/templates/ingress.yaml similarity index 84% rename from charts/jans/charts/nginx-ingress/templates/ingress.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/templates/ingress.yaml index 35013808d50..0a7007bf0b7 100644 --- a/charts/jans/charts/nginx-ingress/templates/ingress.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/templates/ingress.yaml @@ -1,5 +1,63 @@ # License terms and conditions for Janssen Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 +{{ if .Values.ingress.adminUiEnabled -}} +{{ $fullName := include "nginx-ingress.fullname" . -}} +{{- $ingressPath := .Values.ingress.path -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }}-admin-ui + labels: + app: {{ $fullName }}-admin-ui +{{- if .Values.ingress.additionalLabels }} +{{ toYaml .Values.ingress.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.ingress.adminUiLabels }} +{{ toYaml .Values.ingress.adminUiLabels | indent 4 }} +{{- end }} + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/rewrite-target: /$2 + nginx.ingress.kubernetes.io/ssl-redirect: "false" + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/proxy-read-timeout: "300" +{{- if .Values.ingress.adminUiAdditionalAnnotations }} +{{ toYaml .Values.ingress.adminUiAdditionalAnnotations | indent 4 }} +{{- end }} +{{- if .Values.ingress.additionalAnnotations }} +{{ toYaml .Values.ingress.additionalAnnotations | indent 4 }} +{{- end }} +spec: +{{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.ingress.hosts }} + {{- $host := . -}} + {{- with $ }} + - host: {{ $host | quote }} + http: + paths: + - path: /admin(|$)(.*) + pathType: Prefix + backend: + service: + name: {{ index .Values "global" "admin-ui" "adminUiServiceName" }} + port: + number: 8080 + {{- end }} + {{- end }} +{{- end }} + +--- + {{ if .Values.ingress.openidConfigEnabled -}} {{ $fullName := include "nginx-ingress.fullname" . -}} {{- $ingressPath := .Values.ingress.path -}} @@ -21,6 +79,9 @@ metadata: nginx.ingress.kubernetes.io/proxy-read-timeout: "300" nginx.ingress.kubernetes.io/configuration-snippet: "rewrite /.well-known/openid-configuration /jans-auth/.well-known/openid-configuration$1 break;" nginx.ingress.kubernetes.io/rewrite-target: /jans-auth/.well-known/openid-configuration +{{- if .Values.ingress.openidAdditionalAnnotations }} +{{ toYaml .Values.ingress.openidAdditionalAnnotations | indent 4 }} +{{- end }} {{- if .Values.ingress.additionalAnnotations }} {{ toYaml .Values.ingress.additionalAnnotations | indent 4 }} {{- end }} @@ -76,6 +137,9 @@ metadata: nginx.ingress.kubernetes.io/proxy-read-timeout: "300" nginx.ingress.kubernetes.io/configuration-snippet: "rewrite /.well-known/uma2-configuration /jans-auth/restv1/uma2-configuration$1 break;" nginx.ingress.kubernetes.io/rewrite-target: /jans-auth/restv1/uma2-configuration +{{- if .Values.ingress.uma2AdditionalAnnotations }} +{{ toYaml .Values.ingress.uma2AdditionalAnnotations | indent 4 }} +{{- end }} {{- if .Values.ingress.additionalAnnotations }} {{ toYaml .Values.ingress.additionalAnnotations | indent 4 }} {{- end }} @@ -131,6 +195,9 @@ metadata: nginx.ingress.kubernetes.io/proxy-read-timeout: "300" nginx.ingress.kubernetes.io/configuration-snippet: "rewrite /.well-known/webfinger /jans-auth/.well-known/webfinger$1 break;" nginx.ingress.kubernetes.io/rewrite-target: /jans-auth/.well-known/webfinger +{{- if .Values.ingress.webfingerAdditionalAnnotations }} +{{ toYaml .Values.ingress.webfingerAdditionalAnnotations | indent 4 }} +{{- end }} {{- if .Values.ingress.additionalAnnotations }} {{ toYaml .Values.ingress.additionalAnnotations | indent 4 }} {{- end }} @@ -186,6 +253,9 @@ metadata: nginx.ingress.kubernetes.io/proxy-read-timeout: "300" nginx.ingress.kubernetes.io/configuration-snippet: "rewrite /.well-known/simple-web-discovery /jans-auth/.well-known/simple-web-discovery$1 break;" nginx.ingress.kubernetes.io/rewrite-target: /jans-auth/.well-known/simple-web-discovery +{{- if .Values.ingress.webdiscoveryAdditionalAnnotations }} +{{ toYaml .Values.ingress.webdiscoveryAdditionalAnnotations | indent 4 }} +{{- end }} {{- if .Values.ingress.additionalAnnotations }} {{ toYaml .Values.ingress.additionalAnnotations | indent 4 }} {{- end }} @@ -241,6 +311,9 @@ metadata: nginx.ingress.kubernetes.io/proxy-read-timeout: "300" nginx.ingress.kubernetes.io/configuration-snippet: "rewrite /.well-known/scim-configuration /jans-scim/restv1/scim-configuration$1 break;" nginx.ingress.kubernetes.io/rewrite-target: /jans-scim/restv1/scim-configuration +{{- if .Values.ingress.scimConfigAdditionalAnnotations }} +{{ toYaml .Values.ingress.scimConfigAdditionalAnnotations | indent 4 }} +{{- end }} {{- if .Values.ingress.additionalAnnotations }} {{ toYaml .Values.ingress.additionalAnnotations | indent 4 }} {{- end }} @@ -294,6 +367,9 @@ metadata: kubernetes.io/ingress.class: "nginx" nginx.org/ssl-services: "scim" nginx.ingress.kubernetes.io/proxy-next-upstream: "error timeout invalid_header http_500 http_502 http_503 http_504" +{{- if .Values.ingress.scimAdditionalAnnotations }} +{{ toYaml .Values.ingress.scimAdditionalAnnotations | indent 4 }} +{{- end }} {{- if .Values.ingress.additionalAnnotations }} {{ toYaml .Values.ingress.additionalAnnotations | indent 4 }} {{- end }} @@ -347,6 +423,9 @@ metadata: kubernetes.io/ingress.class: "nginx" nginx.org/ssl-services: "configapi" nginx.ingress.kubernetes.io/proxy-next-upstream: "error timeout invalid_header http_500 http_502 http_503 http_504" +{{- if .Values.ingress.configApiAdditionalAnnotations }} +{{ toYaml .Values.ingress.configApiAdditionalAnnotations | indent 4 }} +{{- end }} {{- if .Values.ingress.additionalAnnotations }} {{ toYaml .Values.ingress.additionalAnnotations | indent 4 }} {{- end }} @@ -402,6 +481,9 @@ metadata: nginx.ingress.kubernetes.io/proxy-read-timeout: "300" nginx.ingress.kubernetes.io/configuration-snippet: "rewrite /.well-known/fido-configuration /jans-auth/restv1/fido-configuration$1 break;" nginx.ingress.kubernetes.io/rewrite-target: /jans-auth/restv1/fido-configuration +{{- if .Values.ingress.u2fAdditionalAnnotations }} +{{ toYaml .Values.ingress.u2fAdditionalAnnotations | indent 4 }} +{{- end }} {{- if .Values.ingress.additionalAnnotations }} {{ toYaml .Values.ingress.additionalAnnotations | indent 4 }} {{- end }} @@ -457,6 +539,9 @@ metadata: nginx.ingress.kubernetes.io/proxy-read-timeout: "300" nginx.ingress.kubernetes.io/configuration-snippet: "rewrite /.well-known/fido2-configuration /jans-fido2/restv1/configuration$1 break;" nginx.ingress.kubernetes.io/rewrite-target: /jans-fido2/restv1/configuration +{{- if .Values.ingress.fido2ConfigAdditionalAnnotations }} +{{ toYaml .Values.ingress.fido2ConfigAdditionalAnnotations | indent 4 }} +{{- end }} {{- if .Values.ingress.additionalAnnotations }} {{ toYaml .Values.ingress.additionalAnnotations | indent 4 }} {{- end }} @@ -510,6 +595,9 @@ metadata: kubernetes.io/ingress.class: "nginx" nginx.org/ssl-services: "auth-server" nginx.ingress.kubernetes.io/proxy-next-upstream: "error timeout invalid_header http_500 http_502 http_503 http_504" +{{- if .Values.ingress.authServerAdditionalAnnotations }} +{{ toYaml .Values.ingress.authServerAdditionalAnnotations | indent 4 }} +{{- end }} {{- if .Values.ingress.additionalAnnotations }} {{ toYaml .Values.ingress.additionalAnnotations | indent 4 }} {{- end }} @@ -563,6 +651,9 @@ metadata: kubernetes.io/ingress.class: "nginx" nginx.org/ssl-services: "auth-server" nginx.ingress.kubernetes.io/proxy-next-upstream: "error timeout invalid_header http_500 http_502 http_503 http_504" +{{- if .Values.ingress.authServerProtectedTokenAdditionalAnnotations }} +{{ toYaml .Values.ingress.authServerProtectedTokenAdditionalAnnotations | indent 4 }} +{{- end }} {{- if .Values.ingress.additionalAnnotations }} {{ toYaml .Values.ingress.additionalAnnotations | indent 4 }} {{- end }} @@ -612,13 +703,16 @@ metadata: {{- if .Values.ingress.additionalLabels }} {{ toYaml .Values.ingress.additionalLabels | indent 4 }} {{- end }} -{{- if .Values.ingress.authServerProtectedRedisterLabels }} -{{ toYaml .Values.ingress.authServerProtectedRedisterLabels | indent 4 }} +{{- if .Values.ingress.authServerProtectedRegisterLabels }} +{{ toYaml .Values.ingress.authServerProtectedRegisterLabels | indent 4 }} {{- end }} annotations: kubernetes.io/ingress.class: "nginx" nginx.org/ssl-services: "auth-server" nginx.ingress.kubernetes.io/proxy-next-upstream: "error timeout invalid_header http_500 http_502 http_503 http_504" +{{- if .Values.ingress.authServerProtectedRegisterAdditionalAnnotations }} +{{ toYaml .Values.ingress.authServerProtectedRegisterAdditionalAnnotations | indent 4 }} +{{- end }} {{- if .Values.ingress.additionalAnnotations }} {{ toYaml .Values.ingress.additionalAnnotations | indent 4 }} {{- end }} diff --git a/charts/jans/charts/nginx-ingress/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/values.yaml similarity index 95% rename from charts/jans/charts/nginx-ingress/values.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/values.yaml index 5d24bc6e14a..e845b6be3b0 100644 --- a/charts/jans/charts/nginx-ingress/values.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/nginx-ingress/values.yaml @@ -1,4 +1,4 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 # Default values for nginx-ingress. nameOverride: "" @@ -67,8 +67,8 @@ ingress: annotations: {} path: / hosts: - - demoexample.jans.io + - demoexample.gluu.org tls: - secretName: tls-certificate hosts: - - demoexample.jans.io + - demoexample.gluu.org diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/.helmignore new file mode 100644 index 00000000000..f0c13194444 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/charts/jans/charts/opendj/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/Chart.yaml similarity index 56% rename from charts/jans/charts/opendj/Chart.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/Chart.yaml index c0ab29b012a..6736646bc3c 100644 --- a/charts/jans/charts/opendj/Chart.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/Chart.yaml @@ -1,21 +1,21 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: v2 name: opendj -version: 1.0.0-b11 -kubeVersion: ">=v1.19.0-0" +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" description: OpenDJ is a directory server which implements a wide range of Lightweight Directory Access Protocol and related standards, including full compliance with LDAPv3 but also support for Directory Service Markup Language (DSMLv2).Written in Java, OpenDJ offers multi-master replication, access control, and many extensions. type: application keywords: - LDAP - OpenDJ -home: https://jans.io +home: https://gluu.org/docs/gluu-server sources: - - https://github.com/JanssenFederation/docker-opendj - - https://github.com/JanssenFederation/cloud-native-edition/tree/master/pyjans/kubernetes/templates/helm/jans/charts/opendj + - https://github.com/GluuFederation/docker-opendj + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/opendj maintainers: - name: Mohammad Abudayyeh - email: support@jans.io + email: support@gluu.org url: https://github.com/moabu -icon: https://jans.io/favicon.ico -appVersion: "1.0.0-b11" \ No newline at end of file +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: "5.0.0" \ No newline at end of file diff --git a/charts/jans/charts/opendj/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/README.md similarity index 75% rename from charts/jans/charts/opendj/README.md rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/README.md index 10281a2aa15..05ebcc322de 100644 --- a/charts/jans/charts/opendj/README.md +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/README.md @@ -1,30 +1,32 @@ # opendj -![Version: 1.0.0-b11](https://img.shields.io/badge/Version-1.0.0--b11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-b11](https://img.shields.io/badge/AppVersion-1.0.0--b11-informational?style=flat-square) +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) OpenDJ is a directory server which implements a wide range of Lightweight Directory Access Protocol and related standards, including full compliance with LDAPv3 but also support for Directory Service Markup Language (DSMLv2).Written in Java, OpenDJ offers multi-master replication, access control, and many extensions. -**Homepage:** +**Homepage:** ## Maintainers | Name | Email | Url | | ---- | ------ | --- | -| Mohammad Abudayyeh | support@jans.io | https://github.com/moabu | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | ## Source Code -* -* +* +* ## Requirements -Kubernetes: `>=v1.19.0-0` +Kubernetes: `>=v1.21.0-0` ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken | +| additionalLabels | object | `{}` | Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} | | dnsConfig | object | `{}` | Add custom dns config | | dnsPolicy | string | `""` | Add custom dns policy | | fullnameOverride | string | `""` | | @@ -38,15 +40,15 @@ Kubernetes: `>=v1.19.0-0` | image.pullSecrets | list | `[]` | Image Pull Secrets | | image.repository | string | `"gluufederation/opendj"` | Image to use for deploying. | | image.tag | string | `"5.0.0_dev"` | Image tag to use for deploying. | -| livenessProbe | object | `{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"failureThreshold":20,"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for OpenDJ if needed. https://github.com/JanssenFederation/docker-opendj/blob/4.3/scripts/healthcheck.py | +| livenessProbe | object | `{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"failureThreshold":20,"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for OpenDJ if needed. https://github.com/GluuFederation/docker-opendj/blob/4.3/scripts/healthcheck.py | | livenessProbe.exec | object | `{"command":["python3","/app/scripts/healthcheck.py"]}` | Executes the python3 healthcheck. | | multiCluster.clusterId | string | `""` | This id needs to be unique to each kubernetes cluster in a multi cluster setup west, east, south, north, region ...etc If left empty it will be randomly generated. | | multiCluster.enabled | bool | `false` | Enable OpenDJ multiCluster mode. This flag enables loading keys under `opendj.multiCluster` | -| multiCluster.namespaceIntId | int | `0` | Namespace int id. This id needs to be a unique number 0-9 per jans installation per namespace. Used when jans is installed in the same kubernetes cluster more than once. | -| multiCluster.replicaCount | int | `1` | The number of opendj non scalabble statefulsets to create. Each pod created must be resolvable as it follows the patterm RELEASE-NAME-opendj-CLUSTERID-regional-{{statefulset pod number}}-{{ $.Values.multiCluster.serfAdvertiseAddrSuffix }} If set to 1, with a release name of jans, the address of the pod would be jans-opendj-regional-0-regional.jans.org | -| multiCluster.serfAdvertiseAddrSuffix | string | `"regional.jans.org:30946"` | OpenDJ Serf advertise address suffix that will be added to each opendj replica. i.e RELEASE-NAME-opendj-regional-{{statefulset pod number}}-{{ $.Values.multiCluster.serfAdvertiseAddrSuffix }} | +| multiCluster.namespaceIntId | int | `0` | Namespace int id. This id needs to be a unique number 0-9 per gluu installation per namespace. Used when gluu is installed in the same kubernetes cluster more than once. | +| multiCluster.replicaCount | int | `1` | The number of opendj non scalabble statefulsets to create. Each pod created must be resolvable as it follows the patterm RELEASE-NAME-opendj-CLUSTERID-regional-{{statefulset pod number}}-{{ $.Values.multiCluster.serfAdvertiseAddrSuffix }} If set to 1, with a release name of gluu, the address of the pod would be gluu-opendj-regional-0-regional.gluu.org | +| multiCluster.serfAdvertiseAddrSuffix | string | `"regional.gluu.org:30946"` | OpenDJ Serf advertise address suffix that will be added to each opendj replica. i.e RELEASE-NAME-opendj-regional-{{statefulset pod number}}-{{ $.Values.multiCluster.serfAdvertiseAddrSuffix }} | | multiCluster.serfKey | string | `"Z51b6PgKU1MZ75NCZOTGGoc0LP2OF3qvF6sjxHyQCYk="` | Serf key. This key will automatically sync across clusters. | -| multiCluster.serfPeers | list | `["jans-opendj-regional-0-regional.jans.org:30946","jans-opendj-regional-0-regional.jans.org:31946"]` | Serf peer addresses. One per cluster. | +| multiCluster.serfPeers | list | `["gluu-opendj-regional-0-regional.gluu.org:30946","gluu-opendj-regional-0-regional.gluu.org:31946"]` | Serf peer addresses. One per cluster. | | nameOverride | string | `""` | | | openDjVolumeMounts.config.mountPath | string | `"/opt/opendj/config"` | | | openDjVolumeMounts.config.name | string | `"opendj-volume"` | | @@ -62,7 +64,7 @@ Kubernetes: `>=v1.19.0-0` | persistence.size | string | `"5Gi"` | OpenDJ volume size | | persistence.type | string | `"DirectoryOrCreate"` | | | ports | object | `{"tcp-admin":{"nodePort":"","port":4444,"protocol":"TCP","targetPort":4444},"tcp-ldap":{"nodePort":"","port":1389,"protocol":"TCP","targetPort":1389},"tcp-ldaps":{"nodePort":"","port":1636,"protocol":"TCP","targetPort":1636},"tcp-repl":{"nodePort":"","port":8989,"protocol":"TCP","targetPort":8989},"tcp-serf":{"nodePort":"","port":7946,"protocol":"TCP","targetPort":7946},"udp-serf":{"nodePort":"","port":7946,"protocol":"UDP","targetPort":7946}}` | servicePorts values used in StatefulSet container | -| readinessProbe | object | `{"failureThreshold":20,"initialDelaySeconds":60,"periodSeconds":25,"tcpSocket":{"port":1636},"timeoutSeconds":5}` | Configure the readiness healthcheck for OpenDJ if needed. https://github.com/JanssenFederation/docker-opendj/blob/4.3/scripts/healthcheck.py | +| readinessProbe | object | `{"failureThreshold":20,"initialDelaySeconds":60,"periodSeconds":25,"tcpSocket":{"port":1636},"timeoutSeconds":5}` | Configure the readiness healthcheck for OpenDJ if needed. https://github.com/GluuFederation/docker-opendj/blob/4.3/scripts/healthcheck.py | | replicas | int | `1` | Service replica number. | | resources | object | `{"limits":{"cpu":"1500m","memory":"2000Mi"},"requests":{"cpu":"1500m","memory":"2000Mi"}}` | Resource specs. | | resources.limits.cpu | string | `"1500m"` | CPU limit. | diff --git a/charts/jans/charts/opendj/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/_helpers.tpl similarity index 100% rename from charts/jans/charts/opendj/templates/_helpers.tpl rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/_helpers.tpl diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/configmaps.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/configmaps.yaml new file mode 100644 index 00000000000..b9cd7c3b192 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/configmaps.yaml @@ -0,0 +1,21 @@ +{{- if .Values.multiCluster.enabled }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-serf-peers + namespace: {{ .Release.Namespace }} + labels: +{{ include "opendj.labels" $ | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +data: + serf-peers-static.json: | + {{ .Values.multiCluster.serfPeers | toJson }} +{{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/cronjobs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/cronjobs.yaml new file mode 100644 index 00000000000..3e108163d15 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/cronjobs.yaml @@ -0,0 +1,101 @@ +{{- if .Values.backup.enabled }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +kind: CronJob +apiVersion: batch/v1beta1 +metadata: + name: {{ include "opendj.fullname" . }}-backup +spec: + schedule: {{ .Values.backup.cronJobSchedule | quote }} + concurrencyPolicy: Forbid + jobTemplate: + spec: + template: + spec: + dnsPolicy: {{ .Values.dnsPolicy | quote }} + {{- with .Values.dnsConfig }} + dnsConfig: + {{ toYaml . | indent 12 }} + {{- end }} + containers: + - name: {{ include "opendj.fullname" . }}-backup + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + envFrom: + - configMapRef: + name: {{ .Release.Name }}-config-cm + ports: + {{- range $key, $value := .Values.ports }} + - containerPort: {{ $value.targetPort }} + name: {{ $key }} + {{- end }} + env: + - name: LDAP_HOST + valueFrom: + configMapKeyRef: + # ConfigMap generated by the Configuration chart when Gluu was installed. This is normally cn. + # Found in Gluu chart under config.configmap.cnConfigKubernetesConfigMap + name: cn + key: ldap_init_host + - name: LDAP_PORT + valueFrom: + configMapKeyRef: + # ConfigMap generated by the Configuration chart when Gluu was installed. This is normally cn. + # Found in Gluu chart under config.configmap.cnConfigKubernetesConfigMap + name: cn + key: ldap_init_port + - name: LDAP_BIND_DN + valueFrom: + configMapKeyRef: + # ConfigMap generated by the Configuration chart when Gluu was installed. This is normally cn. + # Found in Gluu chart under config.configmap.cnConfigKubernetesConfigMap + name: cn + key: ldap_site_binddn + - name: LDAP_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-ldap-cron-pass + key: password + # while true; do sleep 60; ldaplog=$(cat /opt/opendj/logs/server.out); startedstr="The Directory Server has started successfully"; if [ -z "${ldaplog##*$startedstr*}" ]; then break; fi; echo "Waiting for opendj server to start"; done + command: + - /bin/sh + - -c + - | + # ========= + # FUNCTIONS + # ========= + + set_java_args() { + # not sure if we can omit `-server` safely + local java_args="-server" + java_args="${java_args} -XX:+UseContainerSupport -XX:MaxRAMPercentage=${GLUU_MAX_RAM_PERCENTAGE} ${GLUU_JAVA_OPTIONS}" + # set the env var so it is loaded by `start-ds` script + export OPENDJ_JAVA_ARGS=${java_args} + } + + # ========== + # ENTRYPOINT + # ========== + + mkdir -p /opt/opendj/locks + + export JAVA_VERSION=$(java -version 2>&1 | awk -F '[\"_]' 'NR==1{print $2}') + + python3 /app/scripts/wait.py + + if [ ! -f /deploy/touched ]; then + python3 /app/scripts/entrypoint.py + touch /deploy/touched + fi + # run OpenDJ server + set_java_args + exec /opt/opendj/bin/start-ds -N & + sleep 300 + RANDOM_NUM=$(cat /dev/urandom | tr -cd '0-5' | head -c 1) + LDAP_BACKUP_FILE=backup-$RANDOM_NUM.ldif + {{- if .Values.multiCluster.enabled }} + /opt/opendj/bin/export-ldif --hostname "$LDAP_HOST" --port "304{{$.Values.multiCluster.namespaceIntId}}0" --bindDN "$LDAP_BIND_DN" --bindPassword "$LDAP_PASSWORD" --backendID userRoot --ldifFile /opt/opendj/ldif/$LDAP_BACKUP_FILE --trustAll + {{- else }} + /opt/opendj/bin/export-ldif --hostname "$LDAP_HOST" --port 4444 --bindDN "$LDAP_BIND_DN" --bindPassword "$LDAP_PASSWORD" --backendID userRoot --ldifFile /opt/opendj/ldif/$LDAP_BACKUP_FILE --trustAll + {{- end }} + restartPolicy: Never +{{- end }} \ No newline at end of file diff --git a/charts/jans/charts/opendj/templates/hpa.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/hpa.yaml similarity index 70% rename from charts/jans/charts/opendj/templates/hpa.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/hpa.yaml index 92e930bfc52..625b98c4ae2 100644 --- a/charts/jans/charts/opendj/templates/hpa.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/hpa.yaml @@ -1,10 +1,19 @@ {{ if .Values.hpa.enabled -}} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: {{ include "opendj.fullname" . }} + labels: +{{ include "opendj.labels" $ | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: scaleTargetRef: apiVersion: apps/v1 diff --git a/charts/jans/charts/opendj/templates/opendj-destination-rules.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/opendj-destination-rules.yaml similarity index 59% rename from charts/jans/charts/opendj/templates/opendj-destination-rules.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/opendj-destination-rules.yaml index 64ead89a923..017ec49f72c 100644 --- a/charts/jans/charts/opendj/templates/opendj-destination-rules.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/opendj-destination-rules.yaml @@ -1,12 +1,21 @@ {{- if or (eq .Values.global.cnPersistenceType "ldap") (eq .Values.global.cnPersistenceType "hybrid") }} {{- if .Values.global.istio.enabled }} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: {{ .Release.Name }}-ldap-mtls namespace: {{.Release.Namespace}} + labels: +{{ include "opendj.labels" $ | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: host: {{ .Values.global.opendj.ldapServiceName }}.{{ .Release.Namespace }}.svc.cluster.local trafficPolicy: diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/secrets.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/secrets.yaml new file mode 100644 index 00000000000..752626fa3d6 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/secrets.yaml @@ -0,0 +1,20 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +{{- if .Values.multiCluster.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-serf-key + labels: +{{ include "opendj.labels" $ | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + serf-key: {{ .Values.multiCluster.serfKey | b64enc }} +{{- end }} \ No newline at end of file diff --git a/charts/jans/charts/opendj/templates/service.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/service.yaml similarity index 91% rename from charts/jans/charts/opendj/templates/service.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/service.yaml index d8f8d3ed063..652d54fb5fe 100644 --- a/charts/jans/charts/opendj/templates/service.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/service.yaml @@ -1,7 +1,7 @@ {{- if or (eq .Values.global.cnPersistenceType "ldap") (eq .Values.global.cnPersistenceType "hybrid") }} {{ range $k, $v := until ( .Values.multiCluster.replicaCount | int ) }} --- -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: v1 kind: Service @@ -17,6 +17,13 @@ metadata: {{- if $.Values.multiCluster.enabled }} appregion: {{ include "opendj.name" $ }}-regional-{{$v}} {{- end }} +{{- if $.Values.additionalLabels }} +{{ toYaml $.Values.additionalLabels | indent 4 }} +{{- end }} +{{- if $.Values.additionalAnnotations }} + annotations: +{{ toYaml $.Values.additionalAnnotations | indent 4 }} +{{- end }} spec: ports: {{- if $.Values.multiCluster.enabled }} diff --git a/charts/jans/charts/opendj/templates/statefulset.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/statefulset.yaml similarity index 84% rename from charts/jans/charts/opendj/templates/statefulset.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/statefulset.yaml index 0fe59a899fd..628ce0e459d 100644 --- a/charts/jans/charts/opendj/templates/statefulset.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/statefulset.yaml @@ -1,7 +1,7 @@ {{- if or (eq .Values.global.cnPersistenceType "ldap") (eq .Values.global.cnPersistenceType "hybrid") }} {{ range $k, $v := until ( .Values.multiCluster.replicaCount | int ) }} --- -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: apps/v1 kind: StatefulSet @@ -17,6 +17,13 @@ metadata: {{- if $.Values.multiCluster.enabled }} appregion: {{ include "opendj.name" $ }}-regional-{{$v}} {{- end }} +{{- if $.Values.additionalLabels }} +{{ toYaml $.Values.additionalLabels | indent 4 }} +{{- end }} +{{- if $.Values.additionalAnnotations }} + annotations: +{{ toYaml $.Values.additionalAnnotations | indent 4 }} +{{- end }} spec: selector: matchLabels: @@ -67,6 +74,11 @@ spec: configMap: name: {{ $.Release.Name }}-serf-peers {{- end }} + {{- if $.Values.global.upgrade.enabled }} + - name: ox-ldif-cm + configMap: + name: {{ $.Release.Name }}-oxjans + {{- end }} containers: - name: {{ include "opendj.name" $ }} imagePullPolicy: {{ $.Values.image.pullPolicy }} @@ -75,13 +87,13 @@ spec: {{- include "opendj.usr-envs" $ | indent 12 }} {{- include "opendj.usr-secret-envs" $ | indent 12 }} {{- if $.Values.multiCluster.enabled }} - - name: JANS_SERF_ADVERTISE_ADDR + - name: GLUU_SERF_ADVERTISE_ADDR value: "{{ $.Release.Name }}-opendj-{{$.Values.multiCluster.clusterId}}-regional-{{$v}}-{{ $.Values.multiCluster.serfAdvertiseAddrSuffix }}:307{{$.Values.multiCluster.namespaceIntId}}{{$v}}" - - name: JANS_LDAP_ADVERTISE_ADMIN_PORT + - name: GLUU_LDAP_ADVERTISE_ADMIN_PORT value: "304{{$.Values.multiCluster.namespaceIntId}}{{$v}}" - - name: JANS_LDAP_ADVERTISE_LDAPS_PORT + - name: GLUU_LDAP_ADVERTISE_LDAPS_PORT value: "306{{$.Values.multiCluster.namespaceIntId}}{{$v}}" - - name: JANS_LDAP_ADVERTISE_REPLICATION_PORT + - name: GLUU_LDAP_ADVERTISE_REPLICATION_PORT value: "309{{$.Values.multiCluster.namespaceIntId}}{{$v}}" {{- end }} lifecycle: @@ -114,13 +126,18 @@ spec: {{- toYaml . | nindent 10 }} {{- end }} {{- if $.Values.multiCluster.enabled }} - - mountPath: "/etc/jans/conf/serf-key" + - mountPath: "/etc/gluu/conf/serf-key" name: serfkey subPath: serf-key - - mountPath: "/etc/jans/conf/serf-peers-static.json" + - mountPath: "/etc/gluu/conf/serf-peers-static.json" name: serfpeers subPath: serf-peers-static.json {{- end }} + {{- if $.Values.global.upgrade.enabled }} + - name: ox-ldif-cm + mountPath: /opt/opendj/config/schema/101-jans.ldif + subPath: 101-jans.ldif + {{- end }} livenessProbe: {{- toYaml $.Values.livenessProbe | nindent 10 }} readinessProbe: diff --git a/charts/jans/charts/opendj/templates/storageclass.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/storageclass.yaml similarity index 86% rename from charts/jans/charts/opendj/templates/storageclass.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/storageclass.yaml index 115bfed3c9d..3af1e452a9e 100644 --- a/charts/jans/charts/opendj/templates/storageclass.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/storageclass.yaml @@ -1,5 +1,4 @@ -{{- if not .Values.global.upgrade.enabled }} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 {{- if or (eq .Values.global.cnPersistenceType "ldap") (eq .Values.global.cnPersistenceType "hybrid") }} apiVersion: storage.k8s.io/v1 @@ -9,7 +8,17 @@ metadata: namespace: {{ .Release.Namespace }} labels: storage: opendj +{{ include "opendj.labels" $ | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-weight": "3" + "helm.sh/hook-delete-policy": before-hook-creation +{{- if .Values.additionalAnnotations }} +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} # Annotation below is to keep the storage class during upgrade. Otherwise, due to the flag at line 1 which is needed, this resource will be deleted. helm.sh/resource-policy: keep storageclass.beta.kubernetes.io/is-default-class: "false" @@ -48,4 +57,3 @@ reclaimPolicy: {{ .Values.global.storageClass.reclaimPolicy }} mountOptions: {{ .Values.global.storageClass.mountOptions | toJson }} allowedTopologies: {{ .Values.global.storageClass.allowedTopologies | toJson }} {{- end }} -{{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/user-custom-secret-envs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/user-custom-secret-envs.yaml new file mode 100644 index 00000000000..61332221a97 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/templates/user-custom-secret-envs.yaml @@ -0,0 +1,22 @@ +{{ if .Values.usrEnvs.secret }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs + labels: +{{ include "opendj.labels" $ | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} \ No newline at end of file diff --git a/charts/jans/charts/opendj/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/values.yaml similarity index 82% rename from charts/jans/charts/opendj/values.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/values.yaml index 7ebeffb723b..66670e44aef 100644 --- a/charts/jans/charts/opendj/values.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/opendj/values.yaml @@ -1,4 +1,4 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 # -- OpenDJ is a directory server which implements a wide range of Lightweight Directory Access Protocol and related standards, including full compliance with LDAPv3 but also support for Directory Service Markup Language (DSMLv2).Written in Java, OpenDJ offers multi-master replication, access control, and many extensions. # -- Configure the HorizontalPodAutoscaler @@ -37,22 +37,22 @@ multiCluster: enabled: false # -- OpenDJ Serf advertise address suffix that will be added to each opendj replica. # i.e RELEASE-NAME-opendj-regional-{{statefulset pod number}}-{{ $.Values.multiCluster.serfAdvertiseAddrSuffix }} - serfAdvertiseAddrSuffix: "regional.jans.org:30946" + serfAdvertiseAddrSuffix: "regional.gluu.org:30946" # -- Serf key. This key will automatically sync across clusters. serfKey: Z51b6PgKU1MZ75NCZOTGGoc0LP2OF3qvF6sjxHyQCYk= # -- Serf peer addresses. One per cluster. serfPeers: - - "jans-opendj-regional-0-regional.jans.org:30946" - - "jans-opendj-regional-0-regional.jans.org:31946" + - "gluu-opendj-regional-0-regional.gluu.org:30946" + - "gluu-opendj-regional-0-regional.gluu.org:31946" # -- The number of opendj non scalabble statefulsets to create. Each pod created must be resolvable as it follows # the patterm RELEASE-NAME-opendj-CLUSTERID-regional-{{statefulset pod number}}-{{ $.Values.multiCluster.serfAdvertiseAddrSuffix }} - # If set to 1, with a release name of jans, the address of the pod would be jans-opendj-regional-0-regional.jans.org + # If set to 1, with a release name of gluu, the address of the pod would be gluu-opendj-regional-0-regional.gluu.org replicaCount: 1 # -- This id needs to be unique to each kubernetes cluster in a multi cluster setup # west, east, south, north, region ...etc If left empty it will be randomly generated. clusterId: "" - # -- Namespace int id. This id needs to be a unique number 0-9 per jans installation per namespace. - # Used when jans is installed in the same kubernetes cluster more than once. + # -- Namespace int id. This id needs to be a unique number 0-9 per gluu installation per namespace. + # Used when gluu is installed in the same kubernetes cluster more than once. namespaceIntId: 0 persistence: # -- OpenDJ volume size @@ -106,7 +106,7 @@ resources: # -- Memory request. memory: 2000Mi # -- Configure the liveness healthcheck for OpenDJ if needed. -# https://github.com/JanssenFederation/docker-opendj/blob/4.3/scripts/healthcheck.py +# https://github.com/GluuFederation/docker-opendj/blob/4.3/scripts/healthcheck.py livenessProbe: # -- Executes the python3 healthcheck. exec: @@ -118,7 +118,7 @@ livenessProbe: timeoutSeconds: 5 failureThreshold: 20 # -- Configure the readiness healthcheck for OpenDJ if needed. -# https://github.com/JanssenFederation/docker-opendj/blob/4.3/scripts/healthcheck.py +# https://github.com/GluuFederation/docker-opendj/blob/4.3/scripts/healthcheck.py readinessProbe: tcpSocket: port: 1636 @@ -150,3 +150,8 @@ openDjVolumeMounts: flag: mountPath: /flag name: opendj-volume + +# -- Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken +additionalAnnotations: { } \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/.helmignore new file mode 100644 index 00000000000..f0c13194444 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/Chart.yaml new file mode 100644 index 00000000000..167945cd6e4 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/Chart.yaml @@ -0,0 +1,23 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v2 +name: oxpassport +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" +description: Gluu interface to Passport.js to support social login and inbound identity. +type: application +keywords: + - Passport.js + - Inbound Identity + - Social login +home: https://gluu.org/docs/gluu-server +sources: + - https://github.com/GluuFederation/gluu-passport + - https://github.com/GluuFederation/docker-oxpassport + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport +maintainers: + - name: Mohammad Abudayyeh + email: support@gluu.org + url: https://github.com/moabu +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: "5.0.0" \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/README.md new file mode 100644 index 00000000000..2b3c78788b7 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/README.md @@ -0,0 +1,69 @@ +# oxpassport + +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) + +Gluu interface to Passport.js to support social login and inbound identity. + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | + +## Source Code + +* +* +* + +## Requirements + +Kubernetes: `>=v1.21.0-0` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken | +| additionalLabels | object | `{}` | Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} | +| affinity | object | `{}` | | +| dnsConfig | object | `{}` | Add custom dns config | +| dnsPolicy | string | `""` | Add custom dns policy | +| fullnameOverride | string | `""` | | +| hpa.behavior | object | `{}` | Scaling Policies | +| hpa.enabled | bool | `true` | | +| hpa.maxReplicas | int | `10` | | +| hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| hpa.minReplicas | int | `1` | | +| hpa.targetCPUUtilizationPercentage | int | `50` | | +| image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| image.pullSecrets | list | `[]` | Image Pull Secrets | +| image.repository | string | `"gluufederation/oxpassport"` | Image to use for deploying. | +| image.tag | string | `"5.0.0_dev"` | Image tag to use for deploying. | +| livenessProbe | object | `{"failureThreshold":20,"httpGet":{"path":"/passport/health-check","port":"http-passport"},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for oxPassport if needed. | +| livenessProbe.httpGet.path | string | `"/passport/health-check"` | http liveness probe endpoint | +| nameOverride | string | `""` | | +| nodeSelector | object | `{}` | | +| readinessProbe | object | `{"failureThreshold":20,"httpGet":{"path":"/passport/health-check","port":"http-passport"},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the oxPassport if needed. | +| readinessProbe.httpGet.path | string | `"/passport/health-check"` | http readiness probe endpoint | +| replicas | int | `1` | Service replica number | +| resources | object | `{"limits":{"cpu":"700m","memory":"900Mi"},"requests":{"cpu":"700m","memory":"900Mi"}}` | Resource specs. | +| resources.limits.cpu | string | `"700m"` | CPU limit. | +| resources.limits.memory | string | `"900Mi"` | Memory limit. | +| resources.requests.cpu | string | `"700m"` | CPU request. | +| resources.requests.memory | string | `"900Mi"` | Memory request. | +| service.name | string | `"http-passport"` | The name of the oxPassport port within the oxPassport service. Please keep it as default. | +| service.port | int | `8090` | Port of the oxPassport service. Please keep it as default. | +| service.sessionAffinity | string | `"None"` | Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP | +| service.sessionAffinityConfig | object | `{"clientIP":{"timeoutSeconds":10800}}` | the maximum session sticky time if sessionAffinity is ClientIP | +| tolerations | list | `[]` | | +| usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0) diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/_helpers.tpl new file mode 100644 index 00000000000..9a8fa719778 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/_helpers.tpl @@ -0,0 +1,68 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "oxpassport.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "oxpassport.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "oxpassport.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* + Common labels +*/}} +{{- define "oxpassport.labels" -}} +app: {{ .Release.Name }}-{{ include "oxpassport.name" . }} +helm.sh/chart: {{ include "oxpassport.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Create user custom defined envs +*/}} +{{- define "oxpassport.usr-envs"}} +{{- range $key, $val := .Values.usrEnvs.normal }} +- name: {{ $key }} + value: {{ $val }} +{{- end }} +{{- end }} + +{{/* +Create user custom defined secret envs +*/}} +{{- define "oxpassport.usr-secret-envs"}} +{{- range $key, $val := .Values.usrEnvs.secret }} +- name: {{ $key }} + valueFrom: + secretKeyRef: + name: {{ $.Release.Name }}-{{ $.Chart.Name }}-user-custom-envs + key: {{ $key }} +{{- end }} +{{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/deployment.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/deployment.yaml new file mode 100644 index 00000000000..75a831f71a3 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/deployment.yaml @@ -0,0 +1,148 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "oxpassport.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "oxpassport.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + replicas: {{ .Values.replicas }} + selector: + matchLabels: + app: {{ .Release.Name }}-{{ include "oxpassport.name" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ include "oxpassport.name" . }} + release: {{ .Release.Name }} + {{- if .Values.global.istio.ingress }} + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + {{- end }} + spec: + {{- with .Values.image.pullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + dnsPolicy: {{ .Values.dnsPolicy | quote }} + {{- with .Values.dnsConfig }} + dnsConfig: +{{ toYaml . | indent 8 }} + {{- end }} + containers: + - name: {{ include "oxpassport.name" . }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + env: + - name: PASSPORT_LOG_LEVEL + value: "info" + {{- include "oxpassport.usr-envs" . | indent 12 }} + {{- include "oxpassport.usr-secret-envs" . | indent 12 }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} + command: + - /bin/sh + - -c + - | + /usr/bin/python3 /scripts/updatelbip.py & + /app/scripts/entrypoint.sh + {{- end }} + ports: + - name: {{ .Values.service.name }} + containerPort: {{ .Values.service.port }} + protocol: TCP + envFrom: + - configMapRef: + name: {{ .Release.Name }}-config-cm + {{ if .Values.global.usrEnvs.secret }} + - secretRef: + name: {{ .Release.Name }}-global-user-custom-envs + {{- end }} + {{ if .Values.global.usrEnvs.normal }} + - configMapRef: + name: {{ .Release.Name }}-global-user-custom-envs + {{- end }} + volumeMounts: + {{- with .Values.volumeMounts }} +{{- toYaml . | nindent 10 }} + {{- end }} + {{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} + - mountPath: {{ .Values.global.cnGoogleApplicationCredentials }} + name: google-sa + subPath: google-credentials.json + {{- end }} + + {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} + - name: {{ include "oxpassport.name" . }}-updatelbip + mountPath: /scripts + {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} + + {{- if not .Values.global.istio.enabled }} + - name: cb-crt + mountPath: "/etc/certs/couchbase.crt" + subPath: couchbase.crt + {{- end }} + {{- end }} + livenessProbe: +{{- toYaml .Values.livenessProbe | nindent 10 }} + readinessProbe: +{{- toYaml .Values.readinessProbe | nindent 10 }} + {{- if or (eq .Values.global.storageClass.provisioner "microk8s.io/hostpath" ) (eq .Values.global.storageClass.provisioner "k8s.io/minikube-hostpath") }} + resources: {} + {{- else if .Values.global.cloud.testEnviroment }} + resources: {} + {{- else }} + resources: +{{- toYaml .Values.resources | nindent 10 }} + {{- end }} + {{- if not .Values.global.isFqdnRegistered }} + hostAliases: + - ip: {{ .Values.global.lbIp }} + hostnames: + - {{ .Values.global.fqdn }} + {{- end }} + volumes: + {{- with .Values.volumes }} +{{- toYaml . | nindent 8 }} + {{- end }} + {{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} + - name: google-sa + secret: + secretName: {{ .Release.Name }}-google-sa + {{- end }} + + {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} + - name: {{ include "oxpassport.name" . }}-updatelbip + configMap: + name: {{ .Release.Name }}-updatelbip + {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} + + {{- if not .Values.global.istio.enabled }} + - name: cb-crt + secret: + secretName: {{ .Release.Name }}-cb-crt + {{- end }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/hpa.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/hpa.yaml new file mode 100644 index 00000000000..dff8d9d10c6 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/hpa.yaml @@ -0,0 +1,38 @@ +{{ if .Values.hpa.enabled -}} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: autoscaling/v1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "oxpassport.fullname" . }} + labels: +{{ include "oxpassport.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "oxpassport.fullname" . }} + minReplicas: {{ .Values.hpa.minReplicas }} + maxReplicas: {{ .Values.hpa.maxReplicas }} + {{- if .Values.hpa.targetCPUUtilizationPercentage }} + targetCPUUtilizationPercentage: {{ .Values.hpa.targetCPUUtilizationPercentage }} + {{- else if .Values.hpa.metrics }} + metrics: + {{- with .Values.hpa.metrics }} +{{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} + {{- if .Values.hpa.behavior }} + behavior: + {{- with .Values.hpa.behavior }} +{{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/oxpassport-destination-rules.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/oxpassport-destination-rules.yaml new file mode 100644 index 00000000000..5c2ddf682fb --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/oxpassport-destination-rules.yaml @@ -0,0 +1,23 @@ +{{- if .Values.global.istio.enabled }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: {{ .Release.Name }}-oxpassport-mtls + namespace: {{.Release.Namespace}} + labels: +{{ include "oxpassport.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + host: {{ .Values.global.oxpassport.oxPassportServiceName }}.{{ .Release.Namespace }}.svc.cluster.local + trafficPolicy: + tls: + mode: ISTIO_MUTUAL +{{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/oxpassport-virtual-services.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/oxpassport-virtual-services.yaml new file mode 100644 index 00000000000..089d78b1069 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/oxpassport-virtual-services.yaml @@ -0,0 +1,34 @@ +{{- if .Values.global.istio.ingress }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: {{ .Release.Name }}-istio-passport + namespace: {{.Release.Namespace}} + labels: +{{ include "oxpassport.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + hosts: + - {{ .Values.global.fqdn }} + gateways: + - {{ .Release.Name }}-global-gtw + http: + - name: {{ .Release.Name }}-istio-passport + match: + - uri: + prefix: "/passport" + route: + - destination: + host: {{ .Values.global.oxpassport.oxPassportServiceName }}.{{ .Release.Namespace }}.svc.cluster.local + port: + number: 8090 + weight: 100 +{{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/service.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/service.yaml new file mode 100644 index 00000000000..582e1b3a103 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/service.yaml @@ -0,0 +1,31 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.global.oxpassport.oxPassportServiceName }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "oxpassport.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + {{- if .Values.global.alb.ingress }} + type: NodePort + {{- end }} + ports: + - port: {{ .Values.service.port }} + name: {{ .Values.service.name }} + selector: + app: {{ .Release.Name }}-{{ include "oxpassport.name" . }} + release: {{ .Release.Name }} + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- with .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: +{{ toYaml . | indent 4 }} + {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/user-custom-secret-envs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/user-custom-secret-envs.yaml new file mode 100644 index 00000000000..05369703d56 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/templates/user-custom-secret-envs.yaml @@ -0,0 +1,22 @@ +{{ if .Values.usrEnvs.secret }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs + labels: +{{ include "oxpassport.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/values.yaml new file mode 100644 index 00000000000..5b6ee1c419a --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxpassport/values.yaml @@ -0,0 +1,98 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +# -- Gluu interface to Passport.js to support social login and inbound identity. +# -- Configure the HorizontalPodAutoscaler +hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} +# -- Add custom normal and secret envs to the service +usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} +# -- Add custom dns policy +dnsPolicy: "" +# -- Add custom dns config +dnsConfig: {} +image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: gluufederation/oxpassport + # -- Image tag to use for deploying. + tag: 5.0.0_dev + # -- Image Pull Secrets + pullSecrets: [ ] +# -- Service replica number +replicas: 1 +# -- Resource specs. +resources: + limits: + # -- CPU limit. + cpu: 700m + # -- Memory limit. + memory: 900Mi + requests: + # -- CPU request. + cpu: 700m + # -- Memory request. + memory: 900Mi +service: + # -- Port of the oxPassport service. Please keep it as default. + port: 8090 + # -- The name of the oxPassport port within the oxPassport service. Please keep it as default. + name: http-passport + # -- Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP + sessionAffinity: None + # -- the maximum session sticky time if sessionAffinity is ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: 10800 + +# -- Configure the liveness healthcheck for oxPassport if needed. +livenessProbe: + httpGet: + # -- http liveness probe endpoint + path: /passport/health-check + port: http-passport + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 20 +# -- Configure the readiness healthcheck for the oxPassport if needed. +readinessProbe: + httpGet: + # -- http readiness probe endpoint + path: /passport/health-check + port: http-passport + initialDelaySeconds: 25 + periodSeconds: 25 + timeoutSeconds: 5 + failureThreshold: 20 +# -- Configure any additional volumes that need to be attached to the pod +volumes: [] +# -- Configure any additional volumesMounts that need to be attached to the containers +volumeMounts: [] + +nameOverride: "" +fullnameOverride: "" + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# -- Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken +additionalAnnotations: { } \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/.helmignore new file mode 100644 index 00000000000..f0c13194444 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/Chart.yaml new file mode 100644 index 00000000000..6918f0b3260 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/Chart.yaml @@ -0,0 +1,22 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v2 +name: oxshibboleth +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" +description: Shibboleth project for the Gluu Server's SAML IDP functionality. +type: application +keywords: + - SAML + - Shibboleth +home: https://gluu.org/docs/gluu-server +sources: + - https://github.com/GluuFederation/oxShibboleth + - https://github.com/GluuFederation/docker-oxshibboleth + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth +maintainers: + - name: Mohammad Abudayyeh + email: support@gluu.org + url: https://github.com/moabu +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: "5.0.0" diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/README.md new file mode 100644 index 00000000000..5cc80086f89 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/README.md @@ -0,0 +1,70 @@ +# oxshibboleth + +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) + +Shibboleth project for the Gluu Server's SAML IDP functionality. + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | + +## Source Code + +* +* +* + +## Requirements + +Kubernetes: `>=v1.21.0-0` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken | +| additionalLabels | object | `{}` | Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} | +| affinity | object | `{}` | | +| dnsConfig | object | `{}` | Add custom dns config | +| dnsPolicy | string | `""` | Add custom dns policy | +| fullnameOverride | string | `""` | | +| hpa.behavior | object | `{}` | Scaling Policies | +| hpa.enabled | bool | `true` | | +| hpa.maxReplicas | int | `10` | | +| hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | +| hpa.minReplicas | int | `1` | | +| hpa.targetCPUUtilizationPercentage | int | `50` | | +| image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| image.pullSecrets | list | `[]` | Image Pull Secrets | +| image.repository | string | `"gluufederation/oxshibboleth"` | Image to use for deploying. | +| image.tag | string | `"5.0.0_dev"` | Image tag to use for deploying. | +| livenessProbe | object | `{"httpGet":{"path":"/idp","port":"http-oxshib"},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for the oxShibboleth if needed. | +| livenessProbe.httpGet.path | string | `"/idp"` | http liveness probe endpoint | +| nameOverride | string | `""` | | +| nodeSelector | object | `{}` | | +| readinessProbe | object | `{"httpGet":{"path":"/idp","port":"http-oxshib"},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the casa if needed. | +| readinessProbe.httpGet.path | string | `"/idp"` | http liveness probe endpoint | +| replicas | int | `1` | Service replica number. | +| resources | object | `{"limits":{"cpu":"1000m","memory":"1000Mi"},"requests":{"cpu":"1000m","memory":"1000Mi"}}` | Resource specs. | +| resources.limits.cpu | string | `"1000m"` | CPU limit. | +| resources.limits.memory | string | `"1000Mi"` | Memory limit. | +| resources.requests.cpu | string | `"1000m"` | CPU request. | +| resources.requests.memory | string | `"1000Mi"` | Memory request. | +| service.name | string | `"http-oxshib"` | Port of the oxShibboleth service. Please keep it as default. | +| service.port | int | `8080` | The name of the oxShibboleth port within the oxPassport service. Please keep it as default. | +| service.sessionAffinity | string | `"None"` | Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP | +| service.sessionAffinityConfig | object | `{"clientIP":{"timeoutSeconds":10800}}` | the maximum session sticky time if sessionAffinity is ClientIP | +| service.targetPort | int | `8080` | | +| tolerations | list | `[]` | | +| usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | +| usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | +| usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | +| volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | +| volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0) diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/_helpers.tpl new file mode 100644 index 00000000000..daa1f2ea713 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/_helpers.tpl @@ -0,0 +1,68 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "oxshibboleth.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "oxshibboleth.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "oxshibboleth.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* + Common labels +*/}} +{{- define "oxshibboleth.labels" -}} +app: {{ .Release.Name }}-{{ include "oxshibboleth.name" . }} +helm.sh/chart: {{ include "oxshibboleth.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Create user custom defined envs +*/}} +{{- define "oxshibboleth.usr-envs"}} +{{- range $key, $val := .Values.usrEnvs.normal }} +- name: {{ $key }} + value: {{ $val }} +{{- end }} +{{- end }} + +{{/* +Create user custom defined secret envs +*/}} +{{- define "oxshibboleth.usr-secret-envs"}} +{{- range $key, $val := .Values.usrEnvs.secret }} +- name: {{ $key }} + valueFrom: + secretKeyRef: + name: {{ $.Release.Name }}-{{ $.Chart.Name }}-user-custom-envs + key: {{ $key }} +{{- end }} +{{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/hpa.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/hpa.yaml new file mode 100644 index 00000000000..4818d6e279d --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/hpa.yaml @@ -0,0 +1,39 @@ +{{ if .Values.hpa.enabled -}} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: autoscaling/v1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "oxshibboleth.fullname" . }} + labels: + APP_NAME: oxshibboleth +{{ include "oxshibboleth.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: StatefulSet + name: {{ include "oxshibboleth.fullname" . }} + minReplicas: {{ .Values.hpa.minReplicas }} + maxReplicas: {{ .Values.hpa.maxReplicas }} + {{- if .Values.hpa.targetCPUUtilizationPercentage }} + targetCPUUtilizationPercentage: {{ .Values.hpa.targetCPUUtilizationPercentage }} + {{- else if .Values.hpa.metrics }} + metrics: + {{- with .Values.hpa.metrics }} +{{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} + {{- if .Values.hpa.behavior }} + behavior: + {{- with .Values.hpa.behavior }} +{{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/oxshibboleth-destination-rules.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/oxshibboleth-destination-rules.yaml new file mode 100644 index 00000000000..c629f0ef948 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/oxshibboleth-destination-rules.yaml @@ -0,0 +1,24 @@ +{{- if .Values.global.istio.enabled }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: {{ .Release.Name }}-oxshibboleth-mtls + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: oxshibboleth +{{ include "oxshibboleth.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + host: {{ .Values.global.oxshibboleth.oxShibbolethServiceName }}.{{ .Release.Namespace }}.svc.cluster.local + trafficPolicy: + tls: + mode: ISTIO_MUTUAL +{{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/oxshibboleth-virtual-services.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/oxshibboleth-virtual-services.yaml new file mode 100644 index 00000000000..b45004c5d73 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/oxshibboleth-virtual-services.yaml @@ -0,0 +1,37 @@ +{{- if .Values.global.istio.ingress }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: {{ .Release.Name }}-istio-oxshibbioleth + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: oxshibboleth +{{ include "oxshibboleth.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + hosts: + - {{ .Values.global.fqdn }} + gateways: + - {{ .Release.Name }}-global-gtw + http: + - name: {{ .Release.Name }}-istio-oxshibbioleth + match: + - uri: + prefix: /idp + rewrite: + uri: /identity + route: + - destination: + host: {{ .Values.global.oxshibboleth.oxShibbolethServiceName }}.{{ .Release.Namespace }}.svc.cluster.local + port: + number: 8080 + weight: 100 +{{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/service.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/service.yaml new file mode 100644 index 00000000000..ebd78e015be --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/service.yaml @@ -0,0 +1,35 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.global.oxshibboleth.oxShibbolethServiceName }} + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: oxshibboleth +{{ include "oxshibboleth.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + {{- if .Values.global.alb.ingress }} + type: NodePort + {{- else }} + clusterIP: None + {{- end }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + name: {{ .Values.service.name }} + selector: + app: {{ .Release.Name }}-{{ include "oxshibboleth.name" . }} + release: {{ .Release.Name }} + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- with .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: +{{ toYaml . | indent 4 }} + {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/statefulset.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/statefulset.yaml new file mode 100644 index 00000000000..640527a22cc --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/statefulset.yaml @@ -0,0 +1,146 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "oxshibboleth.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: oxshibboleth +{{ include "oxshibboleth.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + serviceName: oxshibboleth + replicas: {{ .Values.replicas }} + selector: + matchLabels: + app: {{ .Release.Name }}-{{ include "oxshibboleth.name" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + APP_NAME: oxshibboleth + app: {{ .Release.Name }}-{{ include "oxshibboleth.name" . }} + release: {{ .Release.Name }} + {{- if .Values.global.istio.ingress }} + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + {{- end }} + spec: + {{- with .Values.image.pullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + dnsPolicy: {{ .Values.dnsPolicy | quote }} + {{- with .Values.dnsConfig }} + dnsConfig: +{{ toYaml . | indent 8 }} + {{- end }} + containers: + - name: {{ include "oxshibboleth.name" . }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + env: + {{- include "oxshibboleth.usr-envs" . | indent 12 }} + {{- include "oxshibboleth.usr-secret-envs" . | indent 12 }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} + command: + - /bin/sh + - -c + - | + /usr/bin/python3 /scripts/updatelbip.py & + /app/scripts/entrypoint.sh + {{- end }} + ports: + - name: {{ .Values.service.name }} + containerPort: {{ .Values.service.port }} + protocol: TCP + envFrom: + - configMapRef: + name: {{ .Release.Name }}-config-cm + {{ if .Values.global.usrEnvs.secret }} + - secretRef: + name: {{ .Release.Name }}-global-user-custom-envs + {{- end }} + {{ if .Values.global.usrEnvs.normal }} + - configMapRef: + name: {{ .Release.Name }}-global-user-custom-envs + {{- end }} + volumeMounts: + {{- with .Values.volumeMounts }} +{{- toYaml . | nindent 12 }} + {{- end }} + {{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} + - mountPath: {{ .Values.global.cnGoogleApplicationCredentials }} + name: google-sa + subPath: google-credentials.json + {{- end }} + + {{- if .Values.global.jackrabbit.enabled }} + - name: cn-jackrabbit-admin-pass + mountPath: /etc/gluu/conf/jackrabbit_admin_password + subPath: jackrabbit_admin_password + {{- end }} + {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} + - name: {{ include "oxshibboleth.fullname" .}}-updatelbip + mountPath: /scripts + {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} + {{- if not .Values.global.istio.enabled }} + - name: cb-crt + mountPath: "/etc/certs/couchbase.crt" + subPath: couchbase.crt + {{- end }} + {{- end }} + livenessProbe: +{{- toYaml .Values.livenessProbe | nindent 10 }} + readinessProbe: +{{- toYaml .Values.readinessProbe | nindent 10 }} + {{- if or (eq .Values.global.storageClass.provisioner "microk8s.io/hostpath" ) (eq .Values.global.storageClass.provisioner "k8s.io/minikube-hostpath") }} + resources: {} + {{- else if .Values.global.cloud.testEnviroment }} + resources: {} + {{- else }} + resources: +{{- toYaml .Values.resources | nindent 10 }} + {{- end }} + {{- if not .Values.global.isFqdnRegistered }} + hostAliases: + - ip: {{ .Values.global.lbIp }} + hostnames: + - {{ .Values.global.fqdn }} + {{- end }} + volumes: + {{- with .Values.volumes }} +{{- toYaml . | nindent 8 }} + {{- end }} + {{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} + - name: google-sa + secret: + secretName: {{ .Release.Name }}-google-sa + {{- end }} + + {{- if .Values.global.jackrabbit.enabled }} + - name: cn-jackrabbit-admin-pass + secret: + secretName: cn-jackrabbit-admin-pass + {{- end }} + {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} + - name: {{ include "oxshibboleth.fullname" .}}-updatelbip + configMap: + name: {{ .Release.Name }}-updatelbip + {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} + + {{- if not .Values.global.istio.enabled }} + - name: cb-crt + secret: + secretName: {{ .Release.Name }}-cb-crt + {{- end }} + {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/user-custom-secret-envs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/user-custom-secret-envs.yaml new file mode 100644 index 00000000000..e126166a94c --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/templates/user-custom-secret-envs.yaml @@ -0,0 +1,23 @@ +{{ if .Values.usrEnvs.secret }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs + labels: + APP_NAME: oxshibboleth +{{ include "oxshibboleth.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/values.yaml new file mode 100644 index 00000000000..118a5312ae8 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/oxshibboleth/values.yaml @@ -0,0 +1,97 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +# -- Shibboleth project for the Gluu Server's SAML IDP functionality. +# -- Configure the HorizontalPodAutoscaler +hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} +# -- Add custom normal and secret envs to the service +usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} +# -- Add custom dns policy +dnsPolicy: "" +# -- Add custom dns config +dnsConfig: {} +image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: gluufederation/oxshibboleth + # -- Image tag to use for deploying. + tag: 5.0.0_dev + # -- Image Pull Secrets + pullSecrets: [ ] +# -- Service replica number. +replicas: 1 +# -- Resource specs. +resources: + limits: + # -- CPU limit. + cpu: 1000m + # -- Memory limit. + memory: 1000Mi + requests: + # -- CPU request. + cpu: 1000m + # -- Memory request. + memory: 1000Mi +service: + # -- The name of the oxShibboleth port within the oxPassport service. Please keep it as default. + port: 8080 + targetPort: 8080 + # -- Port of the oxShibboleth service. Please keep it as default. + name: http-oxshib + # -- Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP + sessionAffinity: None + # -- the maximum session sticky time if sessionAffinity is ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: 10800 + +# -- Configure the liveness healthcheck for the oxShibboleth if needed. +livenessProbe: + httpGet: + # -- http liveness probe endpoint + path: /idp + port: http-oxshib + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 +# -- Configure the readiness healthcheck for the casa if needed. +readinessProbe: + httpGet: + # -- http liveness probe endpoint + path: /idp + port: http-oxshib + initialDelaySeconds: 25 + periodSeconds: 25 + timeoutSeconds: 5 +# -- Configure any additional volumes that need to be attached to the pod +volumes: [] +# -- Configure any additional volumesMounts that need to be attached to the containers +volumeMounts: [] + +nameOverride: "" +fullnameOverride: "" + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# -- Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken +additionalAnnotations: { } \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/.helmignore new file mode 100644 index 00000000000..50af0317254 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/Chart.yaml new file mode 100644 index 00000000000..b6bc749e0b3 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/Chart.yaml @@ -0,0 +1,21 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v2 +name: persistence +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" +description: Job to generate data and initial config for Gluu Server persistence layer. +type: application +keywords: + - persistence prep +home: https://gluu.org/docs/gluu-server +sources: + - https://github.com/JanssenProject/docker-jans-persistence-loader + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/persistence +maintainers: + - name: Mohammad Abudayyeh + email: support@gluu.org + url: https://github.com/moabu +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: "5.0.0" + diff --git a/charts/jans/charts/persistence/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/README.md similarity index 61% rename from charts/jans/charts/persistence/README.md rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/README.md index 87906df5006..a4e5547911d 100644 --- a/charts/jans/charts/persistence/README.md +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/README.md @@ -1,37 +1,39 @@ # persistence -![Version: 1.0.0-b11](https://img.shields.io/badge/Version-1.0.0--b11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-b11](https://img.shields.io/badge/AppVersion-1.0.0--b11-informational?style=flat-square) +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) -Job to generate data and initial config for Janssen Server persistence layer. +Job to generate data and initial config for Gluu Server persistence layer. -**Homepage:** +**Homepage:** ## Maintainers | Name | Email | Url | | ---- | ------ | --- | -| Mohammad Abudayyeh | support@jans.io | https://github.com/moabu | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | ## Source Code * -* +* ## Requirements -Kubernetes: `>=v1.19.0-0` +Kubernetes: `>=v1.21.0-0` ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken | +| additionalLabels | object | `{}` | Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} | | dnsConfig | object | `{}` | Add custom dns config | | dnsPolicy | string | `""` | Add custom dns policy | | fullnameOverride | string | `""` | | | image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | | image.pullSecrets | list | `[]` | Image Pull Secrets | | image.repository | string | `"gluufederation/persistence"` | Image to use for deploying. | -| image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | +| image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | | imagePullSecrets | list | `[]` | | | nameOverride | string | `""` | | | resources | object | `{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}}` | Resource specs. | diff --git a/charts/jans/charts/persistence/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/templates/_helpers.tpl similarity index 100% rename from charts/jans/charts/persistence/templates/_helpers.tpl rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/templates/_helpers.tpl diff --git a/charts/jans/charts/persistence/templates/jobs.yml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/templates/jobs.yml similarity index 78% rename from charts/jans/charts/persistence/templates/jobs.yml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/templates/jobs.yml index 8aa96720323..0ee6515e712 100644 --- a/charts/jans/charts/persistence/templates/jobs.yml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/templates/jobs.yml @@ -1,4 +1,4 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: batch/v1 kind: Job @@ -8,7 +8,15 @@ metadata: labels: APP_NAME: persistence-loader {{ include "persistence.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: + ttlSecondsAfterFinished: 120 template: metadata: name: {{ include "persistence.name" . }} @@ -61,51 +69,39 @@ spec: {{- with .Values.volumeMounts }} {{- toYaml . | nindent 10 }} {{- end }} + {{- if .Values.global.jackrabbit.enabled }} + - name: cn-jackrabbit-admin-pass + mountPath: /etc/gluu/conf/jackrabbit_admin_password + subPath: jackrabbit_admin_password + {{- end }} {{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} - mountPath: {{ .Values.global.cnGoogleApplicationCredentials }} name: google-sa subPath: google-credentials.json {{- end }} {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} - - name: cb-pass - mountPath: "/etc/jans/conf/couchbase_password" - subPath: couchbase_password - - name: cb-super-pass - mountPath: "/etc/jans/conf/couchbase_superuser_password" - subPath: couchbase_superuser_password - name: cb-crt mountPath: "/etc/certs/couchbase.crt" subPath: couchbase.crt {{- end }} - {{- if eq .Values.global.cnPersistenceType "sql" }} - - name: sql-pass - mountPath: "/etc/jans/conf/sql_password" - subPath: sql_password - {{- end }} resources: {{- toYaml .Values.resources | nindent 10 }} volumes: {{- with .Values.volumes }} {{- toYaml . | nindent 8 }} {{- end }} + {{- if .Values.global.jackrabbit.enabled }} + - name: cn-jackrabbit-admin-pass + secret: + secretName: cn-jackrabbit-admin-pass + {{- end }} {{ if or (eq .Values.global.configSecretAdapter "google") (eq .Values.global.cnPersistenceType "spanner") }} - name: google-sa secret: secretName: {{ .Release.Name }}-google-sa {{- end }} {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} - - name: cb-pass - secret: - secretName: {{ .Release.Name }}-cb-pass - - name: cb-super-pass - secret: - secretName: {{ .Release.Name }}-superuser-cb-pass - name: cb-crt secret: secretName: {{ .Release.Name }}-cb-crt {{- end }} - {{- if eq .Values.global.cnPersistenceType "sql" }} - - name: sql-pass - secret: - secretName: {{ .Release.Name }}-sql-pass - {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/templates/service.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/templates/service.yaml new file mode 100644 index 00000000000..b266650a69e --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/templates/service.yaml @@ -0,0 +1,27 @@ +{{- if .Values.global.istio.enabled }} +# License terms and conditions: +# https://www.apache.org/licenses/LICENSE-2.0 +# Used with Istio +apiVersion: v1 +kind: Service +metadata: + name: {{ include "persistence.fullname" . }} + labels: + APP_NAME: persistence-loader +{{ include "persistence.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + ports: + - name: http + port: 80 + targetPort: 8080 + selector: + app: {{ .Release.Name }}-{{ include "persistence.name" . }} + type: ClusterIP +{{- end }} \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/templates/user-custom-secret-envs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/templates/user-custom-secret-envs.yaml new file mode 100644 index 00000000000..b8b3b87e8ca --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/templates/user-custom-secret-envs.yaml @@ -0,0 +1,22 @@ +{{ if .Values.usrEnvs.secret }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs + labels: +{{ include "persistence.labels" . | indent 4 }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} \ No newline at end of file diff --git a/charts/jans/charts/persistence/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/values.yaml similarity index 69% rename from charts/jans/charts/persistence/values.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/values.yaml index e25592bf8eb..6cd59a71e13 100644 --- a/charts/jans/charts/persistence/values.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/persistence/values.yaml @@ -1,6 +1,6 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 -# -- Job to generate data and initial config for Janssen Server persistence layer. +# -- Job to generate data and initial config for Gluu Server persistence layer. # -- Add custom normal and secret envs to the service usrEnvs: # -- Add custom normal envs to the service @@ -19,7 +19,7 @@ image: # -- Image to use for deploying. repository: gluufederation/persistence # -- Image tag to use for deploying. - tag: 1.0.0_b11 + tag: 1.0.0-beta.14 # -- Image Pull Secrets pullSecrets: [ ] # -- Resource specs. @@ -42,3 +42,8 @@ volumeMounts: [] imagePullSecrets: [] nameOverride: "" fullnameOverride: "" + +# -- Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken +additionalAnnotations: { } \ No newline at end of file diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/.helmignore b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/.helmignore new file mode 100644 index 00000000000..f0c13194444 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/Chart.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/Chart.yaml new file mode 100644 index 00000000000..5b003c3110c --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/Chart.yaml @@ -0,0 +1,23 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v2 +name: scim +version: 5.0.2 +kubeVersion: ">=v1.21.0-0" +description: System for Cross-domain Identity Management (SCIM) version 2.0 +type: application +keywords: + - SCIM + - API +home: https://gluu.org/docs/gluu-server +sources: + - https://github.com/JanssenProject/jans-scim + - https://gluu.org/docs/gluu-server/api-guide/scim-api/ + - https://github.com/JanssenProject/docker-jans-scim + - https://github.com/GluuFederation/cloud-native-edition/tree/master/pygluu/kubernetes/templates/helm/gluu/charts/scim +maintainers: + - name: Mohammad Abudayyeh + email: support@gluu.org + url: https://github.com/moabu +icon: https://gluu.org/docs/gluu-server/favicon.ico +appVersion: "5.0.0" diff --git a/charts/jans/charts/scim/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/README.md similarity index 66% rename from charts/jans/charts/scim/README.md rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/README.md index a6cf5449dde..4f12e202703 100644 --- a/charts/jans/charts/scim/README.md +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/README.md @@ -1,32 +1,34 @@ # scim -![Version: 1.0.0-b11](https://img.shields.io/badge/Version-1.0.0--b11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-b11](https://img.shields.io/badge/AppVersion-1.0.0--b11-informational?style=flat-square) +![Version: 5.0.2](https://img.shields.io/badge/Version-5.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) System for Cross-domain Identity Management (SCIM) version 2.0 -**Homepage:** +**Homepage:** ## Maintainers | Name | Email | Url | | ---- | ------ | --- | -| Mohammad Abudayyeh | support@jans.io | https://github.com/moabu | +| Mohammad Abudayyeh | support@gluu.org | https://github.com/moabu | ## Source Code * -* +* * -* +* ## Requirements -Kubernetes: `>=v1.19.0-0` +Kubernetes: `>=v1.21.0-0` ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| +| additionalAnnotations | object | `{}` | Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken | +| additionalLabels | object | `{}` | Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} | | dnsConfig | object | `{}` | Add custom dns config | | dnsPolicy | string | `""` | Add custom dns policy | | hpa.behavior | object | `{}` | Scaling Policies | @@ -38,7 +40,7 @@ Kubernetes: `>=v1.19.0-0` | image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | | image.pullSecrets | list | `[]` | Image Pull Secrets | | image.repository | string | `"janssenproject/scim"` | Image to use for deploying. | -| image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | +| image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | | livenessProbe | object | `{"httpGet":{"path":"/jans-scim/sys/health-check","port":8080},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for SCIM if needed. | | livenessProbe.httpGet.path | string | `"/jans-scim/sys/health-check"` | http liveness probe endpoint | | readinessProbe | object | `{"httpGet":{"path":"/jans-scim/sys/health-check","port":8080},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the SCIM if needed. | @@ -50,6 +52,8 @@ Kubernetes: `>=v1.19.0-0` | resources.requests.memory | string | `"1000Mi"` | Memory request. | | service.name | string | `"http-scim"` | The name of the scim port within the scim service. Please keep it as default. | | service.port | int | `8080` | Port of the scim service. Please keep it as default. | +| service.sessionAffinity | string | `"None"` | Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP | +| service.sessionAffinityConfig | object | `{"clientIP":{"timeoutSeconds":10800}}` | the maximum session sticky time if sessionAffinity is ClientIP | | usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | | usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | | usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | diff --git a/charts/jans/charts/scim/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/_helpers.tpl similarity index 100% rename from charts/jans/charts/scim/templates/_helpers.tpl rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/_helpers.tpl diff --git a/charts/jans/charts/scim/templates/deployment.yml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/deployment.yml similarity index 83% rename from charts/jans/charts/scim/templates/deployment.yml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/deployment.yml index 843b958b754..159dec57911 100644 --- a/charts/jans/charts/scim/templates/deployment.yml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/deployment.yml @@ -1,4 +1,4 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: apps/v1 kind: Deployment @@ -8,6 +8,13 @@ metadata: labels: APP_NAME: scim {{ include "scim.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: replicas: {{ .Values.replicas }} selector: @@ -81,24 +88,16 @@ spec: name: google-sa subPath: google-credentials.json {{- end }} - {{- if eq .Values.global.cnPersistenceType "sql" }} - - name: sql-pass - mountPath: "/etc/jans/conf/sql_password" - subPath: sql_password - {{- end }} {{- if and (not .Values.global.isFqdnRegistered ) (or (eq .Values.global.storageClass.provisioner "kubernetes.io/aws-ebs") (eq .Values.global.storageClass.provisioner "openebs.io/local")) }} - - name: {{ include "scim.fullname" .}}-updatelbip - mountPath: "/scripts" + - name: {{ include "scim.fullname" .}}-updatelbip + mountPath: "/scripts" {{- end }} {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} - - name: cb-pass - mountPath: "/etc/jans/conf/couchbase_password" - subPath: couchbase_password - {{- if not .Values.global.istio.enabled }} - - name: cb-crt - mountPath: "/etc/certs/couchbase.crt" - subPath: couchbase.crt - {{- end }} + {{- if not .Values.global.istio.enabled }} + - name: cb-crt + mountPath: "/etc/certs/couchbase.crt" + subPath: couchbase.crt + {{- end }} {{- end }} livenessProbe: {{- toYaml .Values.livenessProbe | nindent 10 }} @@ -119,15 +118,9 @@ spec: secret: secretName: {{ .Release.Name }}-google-sa {{- end }} - {{- if eq .Values.global.cnPersistenceType "sql" }} - - name: sql-pass - secret: - secretName: {{ .Release.Name }}-sql-pass - {{- end }} + {{- if or (eq .Values.global.cnPersistenceType "couchbase") (eq .Values.global.cnPersistenceType "hybrid") }} - - name: cb-pass - secret: - secretName: {{ .Release.Name }}-cb-pass + {{- if not .Values.global.istio.enabled }} - name: cb-crt secret: diff --git a/charts/jans/charts/scim/templates/hpa.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/hpa.yaml similarity index 68% rename from charts/jans/charts/scim/templates/hpa.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/hpa.yaml index 22635adb893..840aa512249 100644 --- a/charts/jans/charts/scim/templates/hpa.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/hpa.yaml @@ -1,10 +1,20 @@ {{ if .Values.hpa.enabled -}} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: {{ include "scim.fullname" . }} + labels: + APP_NAME: scim +{{ include "scim.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: scaleTargetRef: apiVersion: apps/v1 diff --git a/charts/jans/charts/scim/templates/scim-destination-rules.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/scim-destination-rules.yaml similarity index 51% rename from charts/jans/charts/scim/templates/scim-destination-rules.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/scim-destination-rules.yaml index db6b677860e..acb5f393dfe 100644 --- a/charts/jans/charts/scim/templates/scim-destination-rules.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/scim-destination-rules.yaml @@ -1,11 +1,21 @@ {{- if .Values.global.istio.enabled }} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: {{ .Release.Name }}-scim-mtls namespace: {{ .Release.Namespace }} + labels: + APP_NAME: scim +{{ include "scim.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: host: {{ .Values.global.scim.scimServiceName }}.{{ .Release.Namespace }}.svc.cluster.local trafficPolicy: diff --git a/charts/jans/charts/scim/templates/scim-virtual-services.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/scim-virtual-services.yaml similarity index 75% rename from charts/jans/charts/scim/templates/scim-virtual-services.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/scim-virtual-services.yaml index 544fdeff5e8..b582edc7fe8 100644 --- a/charts/jans/charts/scim/templates/scim-virtual-services.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/scim-virtual-services.yaml @@ -1,11 +1,21 @@ {{- if .Values.global.istio.ingress }} -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: {{ .Release.Name }}-istio-scim-config namespace: {{ .Release.Namespace }} + labels: + APP_NAME: scim +{{ include "scim.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} spec: hosts: - {{ .Values.global.fqdn }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/service.yml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/service.yml new file mode 100644 index 00000000000..c964cc3e713 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/service.yml @@ -0,0 +1,31 @@ +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.global.scim.scimServiceName }} + namespace: {{ .Release.Namespace }} + labels: + APP_NAME: scim +{{ include "scim.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +spec: + {{- if .Values.global.alb.ingress }} + type: NodePort + {{- end }} + ports: + - port: {{ .Values.service.port }} + name: {{ .Values.service.name }} + selector: + app: {{ .Release.Name }}-{{ include "scim.name" . }} #scim + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- with .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: +{{ toYaml . | indent 4 }} + {{- end }} diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/user-custom-secret-envs.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/user-custom-secret-envs.yaml new file mode 100644 index 00000000000..01dda2bf16a --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/templates/user-custom-secret-envs.yaml @@ -0,0 +1,23 @@ +{{ if .Values.usrEnvs.secret }} +# License terms and conditions for Gluu Cloud Native Edition: +# https://www.apache.org/licenses/LICENSE-2.0 +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Chart.Name }}-user-custom-envs + labels: + APP_NAME: scim +{{ include "scim.labels" . | indent 4}} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 4 }} +{{- end }} +{{- if .Values.additionalAnnotations }} + annotations: +{{ toYaml .Values.additionalAnnotations | indent 4 }} +{{- end }} +type: Opaque +data: + {{- range $key, $val := .Values.usrEnvs.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +{{- end}} \ No newline at end of file diff --git a/charts/jans/charts/scim/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/values.yaml similarity index 71% rename from charts/jans/charts/scim/values.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/values.yaml index 62f645b1b46..7146c8586ab 100644 --- a/charts/jans/charts/scim/values.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/charts/scim/values.yaml @@ -1,4 +1,4 @@ -# License terms and conditions for Janssen Cloud Native Edition: +# License terms and conditions for Gluu Cloud Native Edition: # https://www.apache.org/licenses/LICENSE-2.0 # -- System for Cross-domain Identity Management (SCIM) version 2.0 # -- Configure the HorizontalPodAutoscaler @@ -29,7 +29,7 @@ image: # -- Image to use for deploying. repository: janssenproject/scim # -- Image tag to use for deploying. - tag: 1.0.0_b11 + tag: 1.0.0-beta.14 # -- Image Pull Secrets pullSecrets: [ ] # -- Service replica number. @@ -50,6 +50,12 @@ service: name: http-scim # -- Port of the scim service. Please keep it as default. port: 8080 + # -- Default set to None If you want to make sure that connections from a particular client are passed to the same Pod each time, you can select the session affinity based on the client's IP addresses by setting this to ClientIP + sessionAffinity: None + # -- the maximum session sticky time if sessionAffinity is ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: 10800 # -- Configure the liveness healthcheck for SCIM if needed. livenessProbe: httpGet: @@ -72,3 +78,8 @@ readinessProbe: volumes: [] # -- Configure any additional volumesMounts that need to be attached to the containers volumeMounts: [] + +# -- Additional labels that will be added across all resources definitions in the format of {mylabel: "myapp"} +additionalLabels: { } +# -- Additional annotations that will be added across all resources in the format of {cert-manager.io/issuer: "letsencrypt-prod"}. key app is taken +additionalAnnotations: { } \ No newline at end of file diff --git a/charts/jans/README.md b/helm/pygluu/kubernetes/templates/helm/gluu/openbanking-helm.md similarity index 55% rename from charts/jans/README.md rename to helm/pygluu/kubernetes/templates/helm/gluu/openbanking-helm.md index a021df69123..036a28da3b9 100644 --- a/charts/jans/README.md +++ b/helm/pygluu/kubernetes/templates/helm/gluu/openbanking-helm.md @@ -1,10 +1,10 @@ -# jans +# gluu -![Version: 1.0.0-b11](https://img.shields.io/badge/Version-1.0.0--b11-informational?style=flat-square) ![AppVersion: 1.0.0-b11](https://img.shields.io/badge/AppVersion-1.0.0--b11-informational?style=flat-square) +![version: 5.0.2](https://img.shields.io/badge/Version-5.0.0-informational?style=flat-square) ![AppVersion: 5.0.0](https://img.shields.io/badge/AppVersion-5.0.0-informational?style=flat-square) -Janssen Access and Identity Management +Gluu Access and Identity Management OpenBanking distribution -**Homepage:** +**Homepage:** ## Maintainers @@ -14,8 +14,8 @@ Janssen Access and Identity Management ## Source Code -* -* +* +* ## Requirements @@ -23,48 +23,27 @@ Kubernetes: `>=v1.17.0-0` | Repository | Name | Version | |------------|------|---------| -| | auth-server | 1.0.0-b11 | -| | auth-server-key-rotation | 1.0.0-b11 | -| | client-api | 1.0.0-b11 | -| | cn-istio-ingress | 1.0.0-b11 | -| | config | 1.0.0-b11 | -| | config-api | 1.0.0-b11 | -| | fido2 | 1.0.0-b11 | -| | nginx-ingress | 1.0.0-b11 | -| | opendj | 1.0.0-b11 | -| | persistence | 1.0.0-b11 | -| | scim | 1.0.0-b11 | +| | auth-server | 5.0.0 | +| | cn-istio-ingress | 5.0.0 | +| | config | 5.0.0 | +| | config-api | 5.0.0 | +| | nginx-ingress | 5.0.0 | +| | persistence | 5.0.0 | ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| -| auth-server | object | `{"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","repository":"janssenproject/auth-server","tag":"1.0.0_b11"},"livenessProbe":{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"readinessProbe":{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"2500m","memory":"2500Mi"},"requests":{"cpu":"2500m","memory":"2500Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | OAuth Authorization Server, the OpenID Connect Provider, the UMA Authorization Server--this is the main Internet facing component of Gluu. It's the service that returns tokens, JWT's and identity assertions. This service must be Internet facing. | -| auth-server-key-rotation | object | `{"dnsConfig":{},"dnsPolicy":"","image":{"pullPolicy":"IfNotPresent","repository":"janssenproject/certmanager","tag":"1.0.0_b11"},"keysLife":48,"resources":{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Responsible for regenerating auth-keys per x hours | -| auth-server-key-rotation.dnsConfig | object | `{}` | Add custom dns config | -| auth-server-key-rotation.dnsPolicy | string | `""` | Add custom dns policy | -| auth-server-key-rotation.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | -| auth-server-key-rotation.image.repository | string | `"janssenproject/certmanager"` | Image to use for deploying. | -| auth-server-key-rotation.image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | -| auth-server-key-rotation.keysLife | int | `48` | Auth server key rotation keys life in hours | -| auth-server-key-rotation.resources | object | `{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}}` | Resource specs. | -| auth-server-key-rotation.resources.limits.cpu | string | `"300m"` | CPU limit. | -| auth-server-key-rotation.resources.limits.memory | string | `"300Mi"` | Memory limit. | -| auth-server-key-rotation.resources.requests.cpu | string | `"300m"` | CPU request. | -| auth-server-key-rotation.resources.requests.memory | string | `"300Mi"` | Memory request. | -| auth-server-key-rotation.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | -| auth-server-key-rotation.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | -| auth-server-key-rotation.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | -| auth-server-key-rotation.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | -| auth-server-key-rotation.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | +| auth-server | object | `{"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","repository":"janssenproject/auth-server","tag":"1.0.0-beta.14"},"livenessProbe":{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"readinessProbe":{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"2500m","memory":"2500Mi"},"requests":{"cpu":"2500m","memory":"2500Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | OAuth Authorization Server, the OpenID Connect Provider, the UMA Authorization Server--this is the main Internet facing component of Gluu. It's the service that returns tokens, JWT's and identity assertions. This service must be Internet facing. | | auth-server.dnsConfig | object | `{}` | Add custom dns config | | auth-server.dnsPolicy | string | `""` | Add custom dns policy | | auth-server.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | | auth-server.hpa.behavior | object | `{}` | Scaling Policies | | auth-server.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | | auth-server.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| auth-server.image.pullSecrets | list | `[]` | Image Pull Secrets | | auth-server.image.repository | string | `"janssenproject/auth-server"` | Image to use for deploying. | -| auth-server.image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | +| auth-server.image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | | auth-server.livenessProbe | object | `{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for the auth server if needed. | | auth-server.livenessProbe.exec | object | `{"command":["python3","/app/scripts/healthcheck.py"]}` | Executes the python3 healthcheck. https://github.com/JanssenProject/docker-jans-auth-server/blob/master/scripts/healthcheck.py | | auth-server.readinessProbe | object | `{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the auth server if needed. https://github.com/JanssenProject/docker-jans-auth-server/blob/master/scripts/healthcheck.py | @@ -79,39 +58,17 @@ Kubernetes: `>=v1.17.0-0` | auth-server.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | | auth-server.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | | auth-server.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | -| client-api | object | `{"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","repository":"janssenproject/client-api","tag":"1.0.0_b11"},"livenessProbe":{"exec":{"command":["curl","-k","https://localhost:8443/health-check"]},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"readinessProbe":{"exec":{"command":["curl","-k","https://localhost:8443/health-check"]},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"1000m","memory":"400Mi"},"requests":{"cpu":"1000m","memory":"400Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Middleware API to help application developers call an OAuth, OpenID or UMA server. You may wonder why this is necessary. It makes it easier for client developers to use OpenID signing and encryption features, without becoming crypto experts. This API provides some high level endpoints to do some of the heavy lifting. | -| client-api.dnsConfig | object | `{}` | Add custom dns config | -| client-api.dnsPolicy | string | `""` | Add custom dns policy | -| client-api.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | -| client-api.hpa.behavior | object | `{}` | Scaling Policies | -| client-api.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | -| client-api.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | -| client-api.image.repository | string | `"janssenproject/client-api"` | Image to use for deploying. | -| client-api.image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | -| client-api.livenessProbe | object | `{"exec":{"command":["curl","-k","https://localhost:8443/health-check"]},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for the auth server if needed. | -| client-api.livenessProbe.exec | object | `{"command":["curl","-k","https://localhost:8443/health-check"]}` | Executes the python3 healthcheck. | -| client-api.readinessProbe | object | `{"exec":{"command":["curl","-k","https://localhost:8443/health-check"]},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the auth server if needed. | -| client-api.replicas | int | `1` | Service replica number. | -| client-api.resources | object | `{"limits":{"cpu":"1000m","memory":"400Mi"},"requests":{"cpu":"1000m","memory":"400Mi"}}` | Resource specs. | -| client-api.resources.limits.cpu | string | `"1000m"` | CPU limit. | -| client-api.resources.limits.memory | string | `"400Mi"` | Memory limit. | -| client-api.resources.requests.cpu | string | `"1000m"` | CPU request. | -| client-api.resources.requests.memory | string | `"400Mi"` | Memory request. | -| client-api.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | -| client-api.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | -| client-api.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | -| client-api.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | -| client-api.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | -| config | object | `{"adminPassword":"Test1234#","city":"Austin","configmap":{"cnCacheType":"NATIVE_PERSISTENCE","cnCasaEnabled":false,"cnClientApiAdminCertCn":"client-api","cnClientApiApplicationCertCn":"client-api","cnClientApiBindIpAddresses":"*","cnConfigGoogleSecretNamePrefix":"gluu","cnConfigGoogleSecretVersionId":"latest","cnConfigKubernetesConfigMap":"cn","cnCouchbaseBucketPrefix":"jans","cnCouchbaseCertFile":"/etc/certs/couchbase.crt","cnCouchbaseCrt":"SWFtTm90YVNlcnZpY2VBY2NvdW50Q2hhbmdlTWV0b09uZQo=","cnCouchbaseIndexNumReplica":0,"cnCouchbasePassword":"P@ssw0rd","cnCouchbasePasswordFile":"/etc/gluu/conf/couchbase_password","cnCouchbaseSuperUser":"admin","cnCouchbaseSuperUserPassword":"Test1234#","cnCouchbaseSuperUserPasswordFile":"/etc/gluu/conf/couchbase_superuser_password","cnCouchbaseUrl":"cbgluu.default.svc.cluster.local","cnCouchbaseUser":"gluu","cnDocumentStoreType":"JCA","cnGoogleProjectId":"google-project-to-save-config-and-secrets-to","cnGoogleSecretManagerPassPhrase":"Test1234#","cnGoogleSecretManagerServiceAccount":"SWFtTm90YVNlcnZpY2VBY2NvdW50Q2hhbmdlTWV0b09uZQo=","cnGoogleSpannerDatabaseId":"","cnGoogleSpannerInstanceId":"","cnJettyRequestHeaderSize":8192,"cnLdapUrl":"opendj:1636","cnMaxRamPercent":"75.0","cnPersistenceLdapMapping":"default","cnRedisSentinelGroup":"","cnRedisSslTruststore":"","cnRedisType":"STANDALONE","cnRedisUrl":"redis.redis.svc.cluster.local:6379","cnRedisUseSsl":false,"cnScimProtectionMode":"OAUTH","cnSecretGoogleSecretNamePrefix":"gluu","cnSecretGoogleSecretVersionId":"latest","cnSecretKubernetesSecret":"cn","cnSqlDbDialect":"mysql","cnSqlDbHost":"my-release-mysql.default.svc.cluster.local","cnSqlDbName":"jans","cnSqlDbPort":3306,"cnSqlDbTimezone":"UTC","cnSqlDbUser":"jans","cnSqlPasswordFile":"/etc/jans/conf/sql_password","cnSqldbUserPassword":"Test1234#","lbAddr":""},"countryCode":"US","dnsConfig":{},"dnsPolicy":"","email":"support@gluu.org","image":{"repository":"janssenproject/configuration-manager","tag":"1.0.0_b11"},"ldapPassword":"P@ssw0rds","orgName":"Gluu","redisPassword":"P@assw0rd","resources":{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}},"state":"TX","usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Configuration parameters for setup and initial configuration secret and config layers used by Gluu services. | -| config-api | object | `{"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","repository":"janssenproject/config-api","tag":"1.0.0_b11"},"livenessProbe":{"httpGet":{"path":"/jans-config-api/api/v1/health/live","port":8074},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"readinessProbe":{"httpGet":{"path":"jans-config-api/api/v1/health/ready","port":8074},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"1000m","memory":"400Mi"},"requests":{"cpu":"1000m","memory":"400Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Config Api endpoints can be used to configure the auth-server, which is an open-source OpenID Connect Provider (OP) and UMA Authorization Server (AS). | +| config | object | `{"adminPassword":"Test1234#","city":"Austin","configmap":{"cnCacheType":"NATIVE_PERSISTENCE","cnCasaEnabled":false,"cnClientApiAdminCertCn":"client-api","cnClientApiApplicationCertCn":"client-api","cnClientApiBindIpAddresses":"*","cnConfigGoogleSecretNamePrefix":"gluu","cnConfigGoogleSecretVersionId":"latest","cnConfigKubernetesConfigMap":"cn","cnCouchbaseBucketPrefix":"jans","cnCouchbaseCertFile":"/etc/certs/couchbase.crt","cnCouchbaseCrt":"SWFtTm90YVNlcnZpY2VBY2NvdW50Q2hhbmdlTWV0b09uZQo=","cnCouchbaseIndexNumReplica":0,"cnCouchbasePassword":"P@ssw0rd","cnCouchbasePasswordFile":"/etc/gluu/conf/couchbase_password","cnCouchbaseSuperUser":"admin","cnCouchbaseSuperUserPassword":"Test1234#","cnCouchbaseSuperUserPasswordFile":"/etc/gluu/conf/couchbase_superuser_password","cnCouchbaseUrl":"cbgluu.default.svc.cluster.local","cnCouchbaseUser":"gluu","cnDocumentStoreType":"JCA","cnGoogleProjectId":"google-project-to-save-config-and-secrets-to","cnGoogleSecretManagerPassPhrase":"Test1234#","cnGoogleSecretManagerServiceAccount":"SWFtTm90YVNlcnZpY2VBY2NvdW50Q2hhbmdlTWV0b09uZQo=","cnGoogleSpannerDatabaseId":"","cnGoogleSpannerInstanceId":"","cnJackrabbitAdminId":"admin","cnJackrabbitAdminIdFile":"/etc/gluu/conf/jackrabbit_admin_id","cnJackrabbitAdminPasswordFile":"/etc/gluu/conf/jackrabbit_admin_password","cnJackrabbitPostgresDatabaseName":"jackrabbit","cnJackrabbitPostgresHost":"postgresql.postgres.svc.cluster.local","cnJackrabbitPostgresPasswordFile":"/etc/gluu/conf/postgres_password","cnJackrabbitPostgresPort":5432,"cnJackrabbitPostgresUser":"jackrabbit","cnJackrabbitSyncInterval":300,"cnJackrabbitUrl":"http://jackrabbit:8080","cnJettyRequestHeaderSize":8192,"cnLdapUrl":"opendj:1636","cnMaxRamPercent":"75.0","cnPassportEnabled":false,"cnPersistenceLdapMapping":"default","cnRedisSentinelGroup":"","cnRedisSslTruststore":"","cnRedisType":"STANDALONE","cnRedisUrl":"redis.redis.svc.cluster.local:6379","cnRedisUseSsl":false,"cnSamlEnabled":false,"cnSecretGoogleSecretNamePrefix":"gluu","cnSecretGoogleSecretVersionId":"latest","cnSecretKubernetesSecret":"cn","cnSqlDbDialect":"mysql","cnSqlDbHost":"my-release-mysql.default.svc.cluster.local","cnSqlDbName":"jans","cnSqlDbPort":3306,"cnSqlDbTimezone":"UTC","cnSqlDbUser":"jans","cnSqlPasswordFile":"/etc/jans/conf/sql_password","cnSqldbUserPassword":"Test1234#","lbAddr":""},"countryCode":"US","dnsConfig":{},"dnsPolicy":"","email":"support@gluu.org","image":{"pullSecrets":[],"repository":"janssenproject/configurator","tag":"1.0.0-beta.14"},"ldapPassword":"P@ssw0rds","migration":{"enabled":false,"migrationDataFormat":"ldif","migrationDir":"/ce-migration"},"orgName":"Gluu","redisPassword":"P@assw0rd","resources":{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}},"state":"TX","usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Configuration parameters for setup and initial configuration secret and config layers used by Gluu services. | +| config-api | object | `{"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"janssenproject/config-api","tag":"1.0.0-beta.14"},"livenessProbe":{"httpGet":{"path":"/jans-config-api/api/v1/health/live","port":8074},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"readinessProbe":{"httpGet":{"path":"jans-config-api/api/v1/health/ready","port":8074},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"1000m","memory":"400Mi"},"requests":{"cpu":"1000m","memory":"400Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Config Api endpoints can be used to configure the auth-server, which is an open-source OpenID Connect Provider (OP) and UMA Authorization Server (AS). | | config-api.dnsConfig | object | `{}` | Add custom dns config | | config-api.dnsPolicy | string | `""` | Add custom dns policy | | config-api.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | | config-api.hpa.behavior | object | `{}` | Scaling Policies | | config-api.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | | config-api.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| config-api.image.pullSecrets | list | `[]` | Image Pull Secrets | | config-api.image.repository | string | `"janssenproject/config-api"` | Image to use for deploying. | -| config-api.image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | +| config-api.image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | | config-api.livenessProbe | object | `{"httpGet":{"path":"/jans-config-api/api/v1/health/live","port":8074},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for the auth server if needed. | | config-api.livenessProbe.httpGet | object | `{"path":"/jans-config-api/api/v1/health/live","port":8074}` | http liveness probe endpoint | | config-api.readinessProbe.httpGet | object | `{"path":"jans-config-api/api/v1/health/ready","port":8074}` | http readiness probe endpoint | @@ -153,12 +110,14 @@ Kubernetes: `>=v1.17.0-0` | config.configmap.cnGoogleSpannerDatabaseId | string | `""` | Google Spanner Database ID. Used only when global.cnPersistenceType is spanner. | | config.configmap.cnJettyRequestHeaderSize | int | `8192` | Jetty header size in bytes in the auth server | | config.configmap.cnMaxRamPercent | string | `"75.0"` | Value passed to Java option -XX:MaxRAMPercentage | +| config.configmap.cnPassportEnabled | bool | `false` | Boolean flag to enable/disable passport chart | | config.configmap.cnPersistenceLdapMapping | string | `"default"` | Specify data that should be saved in LDAP (one of default, user, cache, site, token, or session; default to default). Note this environment only takes effect when `global.cnPersistenceType` is set to `hybrid`. | | config.configmap.cnRedisSentinelGroup | string | `""` | Redis Sentinel Group. Often set when `config.configmap.cnRedisType` is set to `SENTINEL`. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | | config.configmap.cnRedisSslTruststore | string | `""` | Redis SSL truststore. Optional. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | | config.configmap.cnRedisType | string | `"STANDALONE"` | Redis service type. `STANDALONE` or `CLUSTER`. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | | config.configmap.cnRedisUrl | string | `"redis.redis.svc.cluster.local:6379"` | Redis URL and port number :. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | | config.configmap.cnRedisUseSsl | bool | `false` | Boolean to use SSL in Redis. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. | +| config.configmap.cnSamlEnabled | bool | `false` | Enable SAML-related features; UI menu, etc. | | config.configmap.cnSecretGoogleSecretNamePrefix | string | `"gluu"` | Prefix for Gluu secret in Google Secret Manager. Defaults to gluu. If left gluu-secret secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google. | | config.configmap.cnSecretKubernetesSecret | string | `"cn"` | Kubernetes secret name holding configuration keys. Used when global.configSecretAdapter is set to kubernetes which is the default. | | config.configmap.cnSqlDbDialect | string | `"mysql"` | SQL database dialect. `mysql` or `pgsql` | @@ -174,9 +133,14 @@ Kubernetes: `>=v1.17.0-0` | config.dnsConfig | object | `{}` | Add custom dns config | | config.dnsPolicy | string | `""` | Add custom dns policy | | config.email | string | `"support@gluu.org"` | Email address of the administrator usually. Used for certificate creation. | -| config.image.repository | string | `"janssenproject/configuration-manager"` | Image to use for deploying. | -| config.image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | +| config.image.pullSecrets | list | `[]` | Image Pull Secrets | +| config.image.repository | string | `"janssenproject/configurator"` | Image to use for deploying. | +| config.image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | | config.ldapPassword | string | `"P@ssw0rds"` | LDAP admin password if OpennDJ is used for persistence. | +| config.migration | object | `{"enabled":false,"migrationDataFormat":"ldif","migrationDir":"/ce-migration"}` | CE to CN Migration section | +| config.migration.enabled | bool | `false` | Boolean flag to enable migration from CE | +| config.migration.migrationDataFormat | string | `"ldif"` | migration data-format depending on persistence backend. Supported data formats are ldif, couchbase+json, spanner+avro, postgresql+json, and mysql+json. | +| config.migration.migrationDir | string | `"/ce-migration"` | Directory holding all migration files | | config.orgName | string | `"Gluu"` | Organization name. Used for certificate creation. | | config.redisPassword | string | `"P@assw0rd"` | Redis admin password if `config.configmap.cnCacheType` is set to `REDIS`. | | config.resources | object | `{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}}` | Resource specs. | @@ -190,43 +154,12 @@ Kubernetes: `>=v1.17.0-0` | config.usrEnvs.secret | object | `{}` | Add custom secret envs to the service. variable1: value1 | | config.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | | config.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | -| fido2 | object | `{"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","repository":"janssenproject/fido2","tag":"1.0.0_b11"},"livenessProbe":{"httpGet":{"path":"/jans-fido2/sys/health-check","port":"http-fido2"},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"readinessProbe":{"httpGet":{"path":"/jans-fido2/sys/health-check","port":"http-fido2"},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"500m","memory":"500Mi"},"requests":{"cpu":"500m","memory":"500Mi"}},"service":{"fido2ServiceName":"fido2"},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | FIDO 2.0 (FIDO2) is an open authentication standard that enables leveraging common devices to authenticate to online services in both mobile and desktop environments. | -| fido2.dnsConfig | object | `{}` | Add custom dns config | -| fido2.dnsPolicy | string | `""` | Add custom dns policy | -| fido2.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | -| fido2.hpa.behavior | object | `{}` | Scaling Policies | -| fido2.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | -| fido2.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | -| fido2.image.repository | string | `"janssenproject/fido2"` | Image to use for deploying. | -| fido2.image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | -| fido2.livenessProbe | object | `{"httpGet":{"path":"/jans-fido2/sys/health-check","port":"http-fido2"},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the liveness healthcheck for the fido2 if needed. | -| fido2.livenessProbe.httpGet | object | `{"path":"/jans-fido2/sys/health-check","port":"http-fido2"}` | http liveness probe endpoint | -| fido2.readinessProbe | object | `{"httpGet":{"path":"/jans-fido2/sys/health-check","port":"http-fido2"},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the readiness healthcheck for the fido2 if needed. | -| fido2.replicas | int | `1` | Service replica number. | -| fido2.resources | object | `{"limits":{"cpu":"500m","memory":"500Mi"},"requests":{"cpu":"500m","memory":"500Mi"}}` | Resource specs. | -| fido2.resources.limits.cpu | string | `"500m"` | CPU limit. | -| fido2.resources.limits.memory | string | `"500Mi"` | Memory limit. | -| fido2.resources.requests.cpu | string | `"500m"` | CPU request. | -| fido2.resources.requests.memory | string | `"500Mi"` | Memory request. | -| fido2.service.fido2ServiceName | string | `"fido2"` | Name of the fido2 service. Please keep it as default. | -| fido2.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | -| fido2.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | -| fido2.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | -| fido2.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | -| fido2.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | -| global | object | `{"alb":{"ingress":false},"auth-server":{"authServerServiceName":"auth-server","enabled":true},"auth-server-key-rotation":{"enabled":false},"awsStorageType":"io1","azureStorageAccountType":"Standard_LRS","azureStorageKind":"Managed","client-api":{"clientApiServerServiceName":"client-api","enabled":false},"cloud":{"testEnviroment":false},"cnGoogleApplicationCredentials":"/etc/jans/conf/google-credentials.json","cnJackrabbitCluster":true,"cnObExtSigningAlias":"","cnObExtSigningJwksCrt":"","cnObExtSigningJwksKey":"","cnObExtSigningJwksKeyPassPhrase":"","cnObExtSigningJwksUri":"","cnObStaticSigningKeyKid":"","cnObTransportAlias":"","cnObTransportCrt":"","cnObTransportKey":"","cnObTransportKeyPassPhrase":"","cnObTransportTrustStore":"","cnPersistenceType":"ldap","config":{"enabled":true},"config-api":{"configApiServerServiceName":"config-api","enabled":true},"configAdapterName":"kubernetes","configSecretAdapter":"kubernetes","cr-rotate":{"enabled":false},"distribution":"default","fido2":{"enabled":false},"fqdn":"demoexample.gluu.org","gcePdStorageType":"pd-standard","isFqdnRegistered":false,"istio":{"enabled":false,"ingress":false,"namespace":"istio-system"},"lbIp":"22.22.22.22","nginx-ingress":{"enabled":true},"opendj":{"enabled":true,"ldapServiceName":"opendj"},"oxshibboleth":{"enabled":false},"persistence":{"enabled":true},"scim":{"enabled":false},"storageClass":{"allowVolumeExpansion":true,"allowedTopologies":[],"mountOptions":["debug"],"parameters":{},"provisioner":"microk8s.io/hostpath","reclaimPolicy":"Retain","volumeBindingMode":"WaitForFirstConsumer"},"upgrade":{"enabled":false},"usrEnvs":{"normal":{},"secret":{}}}` | Parameters used globally across all services helm charts. | +| global | object | `{"alb":{"ingress":false},"auth-server":{"authServerServiceName":"auth-server","enabled":true},"auth-server-key-rotation":{"enabled":false},"awsStorageType":"io1","azureStorageAccountType":"Standard_LRS","azureStorageKind":"Managed","client-api":{"clientApiServerServiceName":"client-api","enabled":false},"cloud":{"testEnviroment":false},"cnGoogleApplicationCredentials":"/etc/jans/conf/google-credentials.json","cnJackrabbitCluster":true,"cnObExtSigningAlias":"","cnObExtSigningJwksCrt":"","cnObExtSigningJwksKey":"","cnObExtSigningJwksKeyPassPhrase":"","cnObExtSigningJwksUri":"","cnObStaticSigningKeyKid":"","cnObTransportAlias":"","cnObTransportCrt":"","cnObTransportKey":"","cnObTransportKeyPassPhrase":"","cnObTransportTrustStore":"","cnPersistenceType":"ldap","config":{"enabled":true},"config-api":{"configApiServerServiceName":"config-api","enabled":true},"configAdapterName":"kubernetes","configSecretAdapter":"kubernetes","cr-rotate":{"enabled":false},"distribution":"default","fido2":{"enabled":false},"fqdn":"demoexample.gluu.org","gcePdStorageType":"pd-standard","isFqdnRegistered":false,"istio":{"enabled":false,"ingress":false,"namespace":"istio-system"},"jackrabbit":{"enabled":false,"jackRabbitServiceName":"jackrabbit"},"lbIp":"","nginx-ingress":{"enabled":true},"opendj":{"enabled":false,"ldapServiceName":"opendj"},"oxshibboleth":{"enabled":false},"persistence":{"enabled":true},"scim":{"enabled":false},"storageClass":{"allowVolumeExpansion":true,"allowedTopologies":[],"mountOptions":["debug"],"parameters":{},"provisioner":"microk8s.io/hostpath","reclaimPolicy":"Retain","volumeBindingMode":"WaitForFirstConsumer"},"upgrade":{"enabled":false},"usrEnvs":{"normal":{},"secret":{}}}` | Parameters used globally across all services helm charts. | | global.alb.ingress | bool | `false` | Activates ALB ingress | -| global.auth-server-key-rotation.enabled | bool | `false` | Boolean flag to enable/disable the auth-server-key rotation cronjob chart. | | global.auth-server.authServerServiceName | string | `"auth-server"` | Name of the auth-server service. Please keep it as default. | | global.auth-server.enabled | bool | `true` | Boolean flag to enable/disable auth-server chart. You should never set this to false. | -| global.awsStorageType | string | `"io1"` | Volume storage type if using AWS volumes. | -| global.azureStorageAccountType | string | `"Standard_LRS"` | Volume storage type if using Azure disks. | -| global.azureStorageKind | string | `"Managed"` | Azure storage kind if using Azure disks | -| global.client-api.clientApiServerServiceName | string | `"client-api"` | Name of the client-api service. Please keep it as default. | -| global.client-api.enabled | bool | `false` | Boolean flag to enable/disable the client-api chart. | | global.cloud.testEnviroment | bool | `false` | Boolean flag if enabled will strip resources requests and limits from all services. | | global.cnGoogleApplicationCredentials | string | `"/etc/jans/conf/google-credentials.json"` | Base64 encoded service account. The sa must have roles/secretmanager.admin to use Google secrets and roles/spanner.databaseUser to use Spanner. | -| global.cnJackrabbitCluster | bool | `true` | Boolean flag if enabled will enable jackrabbit in cluster mode with Postgres. | | global.cnObExtSigningAlias | string | `""` | Open banking external signing AS Alias. This is a kid value.Used in SSA Validation, kid used while encoding a JWT sent to token URL i.e XkwIzWy44xWSlcWnMiEc8iq9s2G | | global.cnObExtSigningJwksCrt | string | `""` | Open banking external signing jwks AS certificate authority string. Used in SSA Validation. This must be encoded using base64.. Used when `.global.cnObExtSigningJwksUri` is set. | | global.cnObExtSigningJwksKey | string | `""` | Open banking external signing jwks AS key string. Used in SSA Validation. This must be encoded using base64. Used when `.global.cnObExtSigningJwksUri` is set. | @@ -238,27 +171,23 @@ Kubernetes: `>=v1.17.0-0` | global.cnObTransportKey | string | `""` | Open banking AS transport key. Used in SSA Validation. This must be encoded using base64. | | global.cnObTransportKeyPassPhrase | string | `""` | Open banking AS transport key passphrase to unlock AS transport key. This must be encoded using base64. | | global.cnObTransportTrustStore | string | `""` | Open banking AS transport truststore crt. This is normally generated from the OB issuing CA, OB Root CA and Signing CA. Used when .global.cnObExtSigningJwksUri is set. Used in SSA Validation. This must be encoded using base64. | -| global.cnPersistenceType | string | `"ldap"` | Persistence backend to run Gluu with ldap|couchbase|hybrid|sql|spanner. | +| global.cnPersistenceType | string | `"sql"` | Persistence backend to run Gluu with ldap|couchbase|hybrid|sql|spanner. | | global.config-api.configApiServerServiceName | string | `"config-api"` | Name of the config-api service. Please keep it as default. | | global.config-api.enabled | bool | `true` | Boolean flag to enable/disable the config-api chart. | | global.config.enabled | bool | `true` | Boolean flag to enable/disable the configuration chart. This normally should never be false | | global.configAdapterName | string | `"kubernetes"` | The config backend adapter that will hold Gluu configuration layer. google|kubernetes | | global.configSecretAdapter | string | `"kubernetes"` | The config backend adapter that will hold Gluu secret layer. google|kubernetes | -| global.cr-rotate.enabled | bool | `false` | Boolean flag to enable/disable the cr-rotate chart. | -| global.distribution | string | `"default"` | Gluu distributions supported are: default|openbanking. | -| global.fido2.enabled | bool | `false` | Boolean flag to enable/disable the fido2 chart. | +| global.distribution | string | `"openbanking"` | Gluu distributions supported are: default|openbanking. | | global.fqdn | string | `"demoexample.gluu.org"` | Fully qualified domain name to be used for Gluu installation. This address will be used to reach Gluu services. | | global.gcePdStorageType | string | `"pd-standard"` | GCE storage kind if using Google disks | | global.isFqdnRegistered | bool | `false` | Boolean flag to enable mapping global.lbIp to global.fqdn inside pods on clouds that provide static ip for loadbalancers. On cloud that provide only addresses to the LB this flag will enable a script to actively scan config.configmap.lbAddr and update the hosts file inside the pods automatically. | | global.istio.enabled | bool | `false` | Boolean flag that enables using istio side cars with Gluu services. | | global.istio.ingress | bool | `false` | Boolean flag that enables using istio gateway for Gluu. This assumes istio ingress is installed and hence the LB is available. | | global.istio.namespace | string | `"istio-system"` | The namespace istio is deployed in. The is normally istio-system. | +| global.lbIp | string | `""` | The Loadbalancer IP created by nginx or istio on clouds that provide static IPs. This is not needed if `global.fqdn` is globally resolvable. | | global.nginx-ingress.enabled | bool | `true` | Boolean flag to enable/disable the nginx-ingress definitions chart. | -| global.opendj.enabled | bool | `true` | Boolean flag to enable/disable the OpenDJ chart. | -| global.opendj.ldapServiceName | string | `"opendj"` | Name of the OpenDJ service. Please keep it as default. | -| global.oxshibboleth.enabled | bool | `false` | Boolean flag to enable/disable the oxShibbboleth chart. | +| global.opendj.enabled | bool | `false` | Boolean flag to enable/disable the OpenDJ chart. | | global.persistence.enabled | bool | `true` | Boolean flag to enable/disable the persistence chart. | -| global.scim.enabled | bool | `false` | Boolean flag to enable/disable the SCIM chart. | | global.storageClass | object | `{"allowVolumeExpansion":true,"allowedTopologies":[],"mountOptions":["debug"],"parameters":{},"provisioner":"microk8s.io/hostpath","reclaimPolicy":"Retain","volumeBindingMode":"WaitForFirstConsumer"}` | StorageClass section for Jackrabbit and OpenDJ charts. This is not currently used by the openbanking distribution. You may specify custom parameters as needed. | | global.storageClass.parameters | object | `{}` | parameters: | | global.upgrade.enabled | bool | `false` | Boolean flag used when running helm upgrade command. This allows upgrading the chart without immutable objects errors. | @@ -294,40 +223,13 @@ Kubernetes: `>=v1.17.0-0` | nginx-ingress.ingress.webdiscoveryLabels | object | `{}` | webdiscovery ingress resource labels. key app is taken | | nginx-ingress.ingress.webfingerEnabled | bool | `true` | Enable endpoint /.well-known/webfinger | | nginx-ingress.ingress.webfingerLabels | object | `{}` | webfinger ingress resource labels. key app is taken | -| opendj | object | `{"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","repository":"gluufederation/opendj","tag":"5.0.0_dev"},"livenessProbe":{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"failureThreshold":20,"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"multiCluster":{"enabled":false,"serfAdvertiseAddr":"firstldap.gluu.org:30946","serfKey":"Z51b6PgKU1MZ75NCZOTGGoc0LP2OF3qvF6sjxHyQCYk=","serfPeers":["firstldap.gluu.org:30946","secondldap.gluu.org:31946"]},"persistence":{"size":"5Gi"},"ports":{"tcp-admin":{"nodePort":"","port":4444,"protocol":"TCP","targetPort":4444},"tcp-ldap":{"nodePort":"","port":1389,"protocol":"TCP","targetPort":1389},"tcp-ldaps":{"nodePort":"","port":1636,"protocol":"TCP","targetPort":1636},"tcp-repl":{"nodePort":"","port":8989,"protocol":"TCP","targetPort":8989},"tcp-serf":{"nodePort":"","port":7946,"protocol":"TCP","targetPort":7946},"udp-serf":{"nodePort":"","port":7946,"protocol":"UDP","targetPort":7946}},"readinessProbe":{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"failureThreshold":20,"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"1500m","memory":"2000Mi"},"requests":{"cpu":"1500m","memory":"2000Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | OpenDJ is a directory server which implements a wide range of Lightweight Directory Access Protocol and related standards, including full compliance with LDAPv3 but also support for Directory Service Markup Language (DSMLv2).Written in Java, OpenDJ offers multi-master replication, access control, and many extensions. | -| opendj.dnsConfig | object | `{}` | Add custom dns config | -| opendj.dnsPolicy | string | `""` | Add custom dns policy | -| opendj.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | -| opendj.hpa.behavior | object | `{}` | Scaling Policies | -| opendj.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | -| opendj.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | -| opendj.image.repository | string | `"gluufederation/opendj"` | Image to use for deploying. | -| opendj.image.tag | string | `"5.0.0_dev"` | Image tag to use for deploying. | -| opendj.livenessProbe | object | `{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"failureThreshold":20,"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for OpenDJ if needed. https://github.com/GluuFederation/docker-opendj/blob/master/scripts/healthcheck.py | -| opendj.livenessProbe.exec | object | `{"command":["python3","/app/scripts/healthcheck.py"]}` | Executes the python3 healthcheck. | -| opendj.multiCluster.enabled | bool | `false` | Enable OpenDJ multiCluster mode. This flag enabbles loading keys under `opendj.multiCluster` | -| opendj.multiCluster.serfAdvertiseAddr | string | `"firstldap.gluu.org:30946"` | OpenDJ Serf advertise address for the cluster | -| opendj.multiCluster.serfKey | string | `"Z51b6PgKU1MZ75NCZOTGGoc0LP2OF3qvF6sjxHyQCYk="` | Serf key. This key will automatically sync across clusters. | -| opendj.multiCluster.serfPeers | list | `["firstldap.gluu.org:30946","secondldap.gluu.org:31946"]` | Serf peer addresses. One per cluster. | -| opendj.persistence.size | string | `"5Gi"` | OpenDJ volume size | -| opendj.readinessProbe | object | `{"exec":{"command":["python3","/app/scripts/healthcheck.py"]},"failureThreshold":20,"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for OpenDJ if needed. https://github.com/GluuFederation/docker-opendj/blob/master/scripts/healthcheck.py | -| opendj.replicas | int | `1` | Service replica number. | -| opendj.resources | object | `{"limits":{"cpu":"1500m","memory":"2000Mi"},"requests":{"cpu":"1500m","memory":"2000Mi"}}` | Resource specs. | -| opendj.resources.limits.cpu | string | `"1500m"` | CPU limit. | -| opendj.resources.limits.memory | string | `"2000Mi"` | Memory limit. | -| opendj.resources.requests.cpu | string | `"1500m"` | CPU request. | -| opendj.resources.requests.memory | string | `"2000Mi"` | Memory request. | -| opendj.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | -| opendj.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | -| opendj.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | -| opendj.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | -| opendj.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | -| persistence | object | `{"dnsConfig":{},"dnsPolicy":"","image":{"pullPolicy":"IfNotPresent","repository":"janssenproject/persistence-loader","tag":"1.0.0_b11"},"resources":{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Job to generate data and intial config for Gluu Server persistence layer. | +| persistence | object | `{"dnsConfig":{},"dnsPolicy":"","image":{"pullPolicy":"IfNotPresent","pullSecrets":[],"repository":"janssenproject/persistence-loader","tag":"1.0.0-beta.14"},"resources":{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | Job to generate data and intial config for Gluu Server persistence layer. | | persistence.dnsConfig | object | `{}` | Add custom dns config | | persistence.dnsPolicy | string | `""` | Add custom dns policy | | persistence.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | +| persistence.image.pullSecrets | list | `[]` | Image Pull Secrets | | persistence.image.repository | string | `"janssenproject/persistence-loader"` | Image to use for deploying. | -| persistence.image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | +| persistence.image.tag | string | `"1.0.0-beta.14"` | Image tag to use for deploying. | | persistence.resources | object | `{"limits":{"cpu":"300m","memory":"300Mi"},"requests":{"cpu":"300m","memory":"300Mi"}}` | Resource specs. | | persistence.resources.limits.cpu | string | `"300m"` | CPU limit | | persistence.resources.limits.memory | string | `"300Mi"` | Memory limit. | @@ -338,30 +240,6 @@ Kubernetes: `>=v1.17.0-0` | persistence.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | | persistence.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | | persistence.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | -| scim | object | `{"dnsConfig":{},"dnsPolicy":"","hpa":{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50},"image":{"pullPolicy":"IfNotPresent","repository":"janssenproject/scim","tag":"1.0.0_b11"},"livenessProbe":{"httpGet":{"path":"/jans-scim/sys/health-check","port":8080},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5},"readinessProbe":{"httpGet":{"path":"/jans-scim/sys/health-check","port":8080},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5},"replicas":1,"resources":{"limits":{"cpu":"1000m","memory":"1000Mi"},"requests":{"cpu":"1000m","memory":"1000Mi"}},"service":{"scimServiceName":"scim"},"usrEnvs":{"normal":{},"secret":{}},"volumeMounts":[],"volumes":[]}` | System for Cross-domain Identity Management (SCIM) version 2.0 | -| scim.dnsConfig | object | `{}` | Add custom dns config | -| scim.dnsPolicy | string | `""` | Add custom dns policy | -| scim.hpa | object | `{"behavior":{},"enabled":true,"maxReplicas":10,"metrics":[],"minReplicas":1,"targetCPUUtilizationPercentage":50}` | Configure the HorizontalPodAutoscaler | -| scim.hpa.behavior | object | `{}` | Scaling Policies | -| scim.hpa.metrics | list | `[]` | metrics if targetCPUUtilizationPercentage is not set | -| scim.image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy to use for deploying. | -| scim.image.repository | string | `"janssenproject/scim"` | Image to use for deploying. | -| scim.image.tag | string | `"1.0.0_b11"` | Image tag to use for deploying. | -| scim.livenessProbe | object | `{"httpGet":{"path":"/jans-scim/sys/health-check","port":8080},"initialDelaySeconds":30,"periodSeconds":30,"timeoutSeconds":5}` | Configure the liveness healthcheck for SCIM if needed. | -| scim.livenessProbe.httpGet.path | string | `"/jans-scim/sys/health-check"` | http liveness probe endpoint | -| scim.readinessProbe | object | `{"httpGet":{"path":"/jans-scim/sys/health-check","port":8080},"initialDelaySeconds":25,"periodSeconds":25,"timeoutSeconds":5}` | Configure the readiness healthcheck for the SCIM if needed. | -| scim.readinessProbe.httpGet.path | string | `"/jans-scim/sys/health-check"` | http readiness probe endpoint | -| scim.replicas | int | `1` | Service replica number. | -| scim.resources.limits.cpu | string | `"1000m"` | CPU limit. | -| scim.resources.limits.memory | string | `"1000Mi"` | Memory limit. | -| scim.resources.requests.cpu | string | `"1000m"` | CPU request. | -| scim.resources.requests.memory | string | `"1000Mi"` | Memory request. | -| scim.service.scimServiceName | string | `"scim"` | Name of the auth-server service. Please keep it as default. | -| scim.usrEnvs | object | `{"normal":{},"secret":{}}` | Add custom normal and secret envs to the service | -| scim.usrEnvs.normal | object | `{}` | Add custom normal envs to the service variable1: value1 | -| scim.usrEnvs.secret | object | `{}` | Add custom secret envs to the service variable1: value1 | -| scim.volumeMounts | list | `[]` | Configure any additional volumesMounts that need to be attached to the containers | -| scim.volumes | list | `[]` | Configure any additional volumes that need to be attached to the pod | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0) diff --git a/charts/jans/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/openbanking-values.yaml similarity index 56% rename from charts/jans/values.yaml rename to helm/pygluu/kubernetes/templates/helm/gluu/openbanking-values.yaml index abb0f29132a..dc0978d40df 100644 --- a/charts/jans/values.yaml +++ b/helm/pygluu/kubernetes/templates/helm/gluu/openbanking-values.yaml @@ -28,7 +28,9 @@ auth-server: # -- Image to use for deploying. repository: janssenproject/auth-server # -- Image tag to use for deploying. - tag: 1.0.0_b11 + tag: 1.0.0-beta.14 + # -- Image Pull Secrets + pullSecrets: [ ] # -- Service replica number. replicas: 1 # -- Resource specs. @@ -68,117 +70,10 @@ auth-server: volumes: [] # -- Configure any additional volumesMounts that need to be attached to the containers volumeMounts: [] - -# -- Responsible for regenerating auth-keys per x hours -auth-server-key-rotation: - # -- Add custom normal and secret envs to the service - usrEnvs: - # -- Add custom normal envs to the service - # variable1: value1 - normal: {} - # -- Add custom secret envs to the service - # variable1: value1 - secret: {} - # -- Add custom dns policy - dnsPolicy: "" - # -- Add custom dns config - dnsConfig: {} - image: - # -- Image pullPolicy to use for deploying. - pullPolicy: IfNotPresent - # -- Image to use for deploying. - repository: janssenproject/certmanager - # -- Image tag to use for deploying. - tag: 1.0.0_b11 - # -- Auth server key rotation keys life in hours - keysLife: 48 - # -- Resource specs. - resources: - limits: - # -- CPU limit. - cpu: 300m - # -- Memory limit. - memory: 300Mi - requests: - # -- CPU request. - cpu: 300m - # -- Memory request. - memory: 300Mi - # -- Configure any additional volumes that need to be attached to the pod - volumes: [] - # -- Configure any additional volumesMounts that need to be attached to the containers - volumeMounts: [] - -# -- Middleware API to help application developers call an OAuth, OpenID or UMA server. You may wonder why this is necessary. It makes it easier for client developers to use OpenID signing and encryption features, without becoming crypto experts. This API provides some high level endpoints to do some of the heavy lifting. -client-api: - # -- Configure the HorizontalPodAutoscaler - hpa: - enabled: true - minReplicas: 1 - maxReplicas: 10 - targetCPUUtilizationPercentage: 50 - # -- metrics if targetCPUUtilizationPercentage is not set - metrics: [] - # -- Scaling Policies - behavior: {} - # -- Add custom normal and secret envs to the service - usrEnvs: - # -- Add custom normal envs to the service - # variable1: value1 - normal: {} - # -- Add custom secret envs to the service - # variable1: value1 - secret: {} - # -- Add custom dns policy - dnsPolicy: "" - # -- Add custom dns config - dnsConfig: {} - image: - # -- Image pullPolicy to use for deploying. - pullPolicy: IfNotPresent - # -- Image to use for deploying. - repository: janssenproject/client-api - # -- Image tag to use for deploying. - tag: 1.0.0_b11 - # -- Service replica number. - replicas: 1 - # -- Resource specs. - resources: - limits: - # -- CPU limit. - cpu: 1000m - # -- Memory limit. - memory: 400Mi - requests: - # -- CPU request. - cpu: 1000m - # -- Memory request. - memory: 400Mi - # -- Configure the liveness healthcheck for the auth server if needed. - livenessProbe: - # -- Executes the python3 healthcheck. - exec: - command: - - curl - - -k - - https://localhost:8443/health-check - initialDelaySeconds: 30 - periodSeconds: 30 - timeoutSeconds: 5 - # -- Configure the readiness healthcheck for the auth server if needed. - readinessProbe: - exec: - command: - - curl - - -k - - https://localhost:8443/health-check - initialDelaySeconds: 25 - periodSeconds: 25 - timeoutSeconds: 5 - # -- Configure any additional volumes that need to be attached to the pod - volumes: [] - # -- Configure any additional volumesMounts that need to be attached to the containers - volumeMounts: [] + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } # -- Configuration parameters for setup and initial configuration secret and config layers used by Gluu services. config: @@ -190,8 +85,6 @@ config: # -- Add custom secret envs to the service. # variable1: value1 secret: {} - # -- Admin password to log in to the UI. - adminPassword: Test1234# # -- City. Used for certificate creation. city: Austin configmap: @@ -215,40 +108,8 @@ config: cnSqldbUserPassword: Test1234# # -- Cache type. `NATIVE_PERSISTENCE`, `REDIS`. or `IN_MEMORY`. Defaults to `NATIVE_PERSISTENCE` . cnCacheType: NATIVE_PERSISTENCE - # -- Enable Casa flag . - cnCasaEnabled: false - # -- Client-api OAuth client admin certificate common name. This should be left to the default value client-api . - cnClientApiAdminCertCn: client-api - # -- Client-api OAuth client application certificate common name. This should be left to the default value client-api. - cnClientApiApplicationCertCn: client-api - # -- Client-api bind address. This limits what ip ranges can access the client-api. This should be left as * and controlled by a NetworkPolicy - cnClientApiBindIpAddresses: "*" # -- The name of the Kubernetes ConfigMap that will hold the configuration layer cnConfigKubernetesConfigMap: cn - # -- The prefix of couchbase buckets. This helps with separation in between different environments and allows for the same couchbase cluster to be used by different setups of Gluu. - cnCouchbaseBucketPrefix: jans - # -- Location of `couchbase.crt` used by Couchbase SDK for tls termination. The file path must end with couchbase.crt. In mTLS setups this is not required. - cnCouchbaseCertFile: /etc/certs/couchbase.crt - # -- Couchbase certificate authority string. This must be encoded using base64. This can also be found in your couchbase UI Security > Root Certificate. In mTLS setups this is not required. - cnCouchbaseCrt: SWFtTm90YVNlcnZpY2VBY2NvdW50Q2hhbmdlTWV0b09uZQo= - # -- The number of replicas per index created. Please note that the number of index nodes must be one greater than the number of index replicas. That means if your couchbase cluster only has 2 index nodes you cannot place the number of replicas to be higher than 1. - cnCouchbaseIndexNumReplica: 0 - # -- Couchbase password for the restricted user config.configmap.cnCouchbaseUser that is often used inside the services. The password must contain one digit, one uppercase letter, one lower case letter and one symbol . - cnCouchbasePassword: P@ssw0rd - # -- The location of the Couchbase restricted user config.configmap.cnCouchbaseUser password. The file path must end with couchbase_password - cnCouchbasePasswordFile: /etc/gluu/conf/couchbase_password - # -- The Couchbase super user (admin) user name. This user is used during initialization only. - cnCouchbaseSuperUser: admin - # -- Couchbase password for the super user config.configmap.cnCouchbaseSuperUser that is used during the initialization process. The password must contain one digit, one uppercase letter, one lower case letter and one symbol - cnCouchbaseSuperUserPassword: Test1234# - # -- The location of the Couchbase restricted user config.configmap.cnCouchbaseSuperUser password. The file path must end with couchbase_superuser_password. - cnCouchbaseSuperUserPasswordFile: /etc/gluu/conf/couchbase_superuser_password - # -- Couchbase URL. Used only when global.cnPersistenceType is hybrid or couchbase. This should be in FQDN format for either remote or local Couchbase clusters. The address can be an internal address inside the kubernetes cluster - cnCouchbaseUrl: cbgluu.default.svc.cluster.local - # -- Couchbase restricted user. Used only when global.cnPersistenceType is hybrid or couchbase. - cnCouchbaseUser: gluu - # -- Document store type to use for shibboleth files JCA or LOCAL. Note that if JCA is selected Apache Jackrabbit will be used. Jackrabbit also enables loading custom files across all services easily. - cnDocumentStoreType: JCA # [google_envs] Envs related to using Google # -- Service account with roles roles/secretmanager.admin base64 encoded string. This is used often inside the services to reach the configuration layer. Used only when global.configAdapterName and global.configSecretAdapter is set to google. cnGoogleSecretManagerServiceAccount: SWFtTm90YVNlcnZpY2VBY2NvdW50Q2hhbmdlTWV0b09uZQo= @@ -273,12 +134,10 @@ config: cnConfigGoogleSecretNamePrefix: gluu # [google_secret_manager_envs] END # [google_envs] END - # -- OpenDJ internal address. Leave as default. Used when `global.cnPersistenceType` is set to `ldap`. - cnLdapUrl: "opendj:1636" # -- Value passed to Java option -XX:MaxRAMPercentage cnMaxRamPercent: "75.0" - # -- Specify data that should be saved in LDAP (one of default, user, cache, site, token, or session; default to default). Note this environment only takes effect when `global.cnPersistenceType` is set to `hybrid`. - cnPersistenceLdapMapping: default + # -- Boolean flag to enable/disable passport chart. Not part of the openbanking distribution. Please leave this disabled. + cnPassportEnabled: false # -- Redis Sentinel Group. Often set when `config.configmap.cnRedisType` is set to `SENTINEL`. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. cnRedisSentinelGroup: "" # -- Redis SSL truststore. Optional. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. @@ -289,22 +148,23 @@ config: cnRedisUrl: "redis.redis.svc.cluster.local:6379" # -- Boolean to use SSL in Redis. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. cnRedisUseSsl: false + # -- Enable SAML-related features; UI menu, etc. Not part of the openbanking distribution. Please leave this disabled. + cnSamlEnabled: false # -- Kubernetes secret name holding configuration keys. Used when global.configSecretAdapter is set to kubernetes which is the default. cnSecretKubernetesSecret: cn # -- Loadbalancer address for AWS if the FQDN is not registered. lbAddr: "" - cnScimProtectionMode: "OAUTH" # -- Country code. Used for certificate creation. countryCode: US # -- Email address of the administrator usually. Used for certificate creation. email: support@gluu.org image: # -- Image to use for deploying. - repository: janssenproject/configuration-manager + repository: janssenproject/configurator # -- Image tag to use for deploying. - tag: 1.0.0_b11 - # -- LDAP admin password if OpennDJ is used for persistence. - ldapPassword: P@ssw0rds + tag: 1.0.0-beta.14 + # -- Image Pull Secrets + pullSecrets: [ ] # -- Organization name. Used for certificate creation. orgName: Gluu # -- Redis admin password if `config.configmap.cnCacheType` is set to `REDIS`. @@ -332,7 +192,10 @@ config: # -- Add custom dns config dnsConfig: {} - + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } # -- Config Api endpoints can be used to configure the auth-server, which is an open-source OpenID Connect Provider (OP) and UMA Authorization Server (AS). config-api: # -- Configure the HorizontalPodAutoscaler @@ -363,7 +226,9 @@ config-api: # -- Image to use for deploying. repository: janssenproject/config-api # -- Image tag to use for deploying. - tag: 1.0.0_b11 + tag: 1.0.0-beta.14 + # -- Image Pull Secrets + pullSecrets: [ ] # -- Service replica number. replicas: 1 # -- Resource specs. @@ -390,7 +255,7 @@ config-api: readinessProbe: # -- http readiness probe endpoint httpGet: - path: jans-config-api/api/v1/health/ready + path: /jans-config-api/api/v1/health/ready port: 8074 initialDelaySeconds: 25 periodSeconds: 25 @@ -400,76 +265,10 @@ config-api: # -- Configure any additional volumesMounts that need to be attached to the containers volumeMounts: [] -# -- FIDO 2.0 (FIDO2) is an open authentication standard that enables leveraging common devices to authenticate to online services in both mobile and desktop environments. -fido2: - # -- Configure the HorizontalPodAutoscaler - hpa: - enabled: true - minReplicas: 1 - maxReplicas: 10 - targetCPUUtilizationPercentage: 50 - # -- metrics if targetCPUUtilizationPercentage is not set - metrics: [] - # -- Scaling Policies - behavior: {} - # -- Add custom normal and secret envs to the service - usrEnvs: - # -- Add custom normal envs to the service - # variable1: value1 - normal: {} - # -- Add custom secret envs to the service - # variable1: value1 - secret: {} - # -- Add custom dns policy - dnsPolicy: "" - # -- Add custom dns config - dnsConfig: {} - image: - # -- Image pullPolicy to use for deploying. - pullPolicy: IfNotPresent - # -- Image to use for deploying. - repository: janssenproject/fido2 - # -- Image tag to use for deploying. - tag: 1.0.0_b11 - # -- Service replica number. - replicas: 1 - # -- Resource specs. - resources: - limits: - # -- CPU limit. - cpu: 500m - # -- Memory limit. - memory: 500Mi - requests: - # -- CPU request. - cpu: 500m - # -- Memory request. - memory: 500Mi - service: - # -- Name of the fido2 service. Please keep it as default. - fido2ServiceName: fido2 - # -- Configure the liveness healthcheck for the fido2 if needed. - livenessProbe: - # -- http liveness probe endpoint - httpGet: - path: /jans-fido2/sys/health-check - port: http-fido2 - initialDelaySeconds: 25 - periodSeconds: 25 - timeoutSeconds: 5 - # -- Configure the readiness healthcheck for the fido2 if needed. - readinessProbe: - httpGet: - path: /jans-fido2/sys/health-check - port: http-fido2 - initialDelaySeconds: 30 - periodSeconds: 30 - timeoutSeconds: 5 - # -- Configure any additional volumes that need to be attached to the pod - volumes: [] - # -- Configure any additional volumesMounts that need to be attached to the containers - volumeMounts: [] - + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } # -- Parameters used globally across all services helm charts. global: # -- Add custom normal and secret envs to the service. @@ -490,6 +289,37 @@ global: authServerServiceName: auth-server # -- Boolean flag to enable/disable auth-server chart. You should never set this to false. enabled: true + # -- App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. + appLoggers: + # -- jans-auth.log target + authLogTarget: "STDOUT" + # -- jans-auth.log level + authLogLevel: "INFO" + # -- http_request_response.log target + httpLogTarget: "FILE" + # -- http_request_response.log level + httpLogLevel: "INFO" + # -- jans-auth_persistence.log target + persistenceLogTarget: "FILE" + # -- jans-auth_persistence.log level + persistenceLogLevel: "INFO" + # -- jans-auth_persistence_duration.log target + persistenceDurationLogTarget: "FILE" + # -- jans-auth_persistence_duration.log level + persistenceDurationLogLevel: "INFO" + # -- jans-auth_persistence_ldap_statistics.log target + ldapStatsLogTarget: "FILE" + # -- jans-auth_persistence_ldap_statistics.log level + ldapStatsLogLevel: "INFO" + # -- jans-auth_script.log target + scriptLogTarget: "FILE" + # -- jans-auth_script.log level + scriptLogLevel: "INFO" + # -- jans-auth_script.log target + auditStatsLogTarget: "FILE" + # -- jans-auth_audit.log level + auditStatsLogLevel: "INFO" + auth-server-key-rotation: # -- Boolean flag to enable/disable the auth-server-key rotation cronjob chart. enabled: false @@ -499,18 +329,27 @@ global: azureStorageAccountType: Standard_LRS # -- Azure storage kind if using Azure disks azureStorageKind: Managed + casa: + # -- Name of the casa service. Please keep it as default. + casaServiceName: casa client-api: # -- Name of the client-api service. Please keep it as default. clientApiServerServiceName: client-api # -- Boolean flag to enable/disable the client-api chart. enabled: false + # -- App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. + appLoggers: + # -- client-api.log target + clientApiLogTarget: "STDOUT" + # -- client-api.log level + clientApiLogLevel: "INFO" cloud: # -- Boolean flag if enabled will strip resources requests and limits from all services. testEnviroment: false # -- Boolean flag if enabled will enable jackrabbit in cluster mode with Postgres. - cnJackrabbitCluster: true + cnJackrabbitCluster: false # -- Persistence backend to run Gluu with ldap|couchbase|hybrid|sql|spanner. - cnPersistenceType: ldap + cnPersistenceType: sql # -- Open banking external signing jwks uri. Used in SSA Validation. cnObExtSigningJwksUri: "" # -- Open banking external signing jwks AS certificate authority string. Used in SSA Validation. This must be encoded using base64.. Used when `.global.cnObExtSigningJwksUri` is set. @@ -527,7 +366,7 @@ global: cnObTransportCrt: "" # -- Open banking AS transport key. Used in SSA Validation. This must be encoded using base64. cnObTransportKey: "" - # -- Open banking AS transport key passphrase to unlock AS transport key. This must be encoded using base64. + # -- Open banking AS transport key pas`sphrase to unlock AS transport key. This must be encoded using base64. cnObTransportKeyPassPhrase: "" # -- Open banking transport Alias used inside the JVM. cnObTransportAlias: "" @@ -547,14 +386,32 @@ global: configApiServerServiceName: config-api # -- Boolean flag to enable/disable the config-api chart. enabled: true + # -- App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. + appLoggers: + # -- configapi.log target + configApiLogTarget: "STDOUT" + # -- configapi.log level + configApiLogLevel: "INFO" cr-rotate: # -- Boolean flag to enable/disable the cr-rotate chart. enabled: false # -- Fully qualified domain name to be used for Gluu installation. This address will be used to reach Gluu services. fqdn: demoexample.gluu.org fido2: + # -- Name of the fido2 service. Please keep it as default. + fido2ServiceName: fido2 # -- Boolean flag to enable/disable the fido2 chart. enabled: false + # -- App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. + appLoggers: + # -- fido2.log target + fido2LogTarget: "STDOUT" + # -- fido2.log level + fido2LogLevel: "INFO" + # -- fido2_persistence.log target + persistenceLogTarget: "FILE" + # -- fido2_persistence.log level + persistenceLogLevel: "INFO" # -- GCE storage kind if using Google disks gcePdStorageType: pd-standard # -- Boolean flag to enable mapping global.lbIp to global.fqdn inside pods on clouds that provide static ip for loadbalancers. On cloud that provide only addresses to the LB this flag will enable a script to actively scan config.configmap.lbAddr and update the hosts file inside the pods automatically. @@ -566,26 +423,52 @@ global: ingress: false # -- The namespace istio is deployed in. The is normally istio-system. namespace: istio-system - lbIp: "22.22.22.22" + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } + jackrabbit: + # -- Boolean flag to enable/disable the jackrabbit chart. For more information on how it is used inside Gluu https://gluu.org/docs/gluu-server/4.2/installation-guide/install-kubernetes/#working-with-jackrabbit. If disabled oxShibboleth cannot be run. + enabled: false + # -- Name of the Jackrabbit service. Please keep it as default. + jackRabbitServiceName: jackrabbit + # -- The Loadbalancer IP created by nginx or istio on clouds that provide static IPs. This is not needed if `global.fqdn` is globally resolvable. + lbIp: 22.22.22.22 nginx-ingress: # -- Boolean flag to enable/disable the nginx-ingress definitions chart. enabled: true - opendj: - # -- Boolean flag to enable/disable the OpenDJ chart. - enabled: true - # -- Name of the OpenDJ service. Please keep it as default. - ldapServiceName: opendj - oxshibboleth: - # -- Boolean flag to enable/disable the oxShibbboleth chart. - enabled: false # -- Gluu distributions supported are: default|openbanking. - distribution: default + distribution: openbanking persistence: # -- Boolean flag to enable/disable the persistence chart. enabled: true scim: + # -- Name of the scim service. Please keep it as default. + scimServiceName: scim # -- Boolean flag to enable/disable the SCIM chart. enabled: false + # -- App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. + appLoggers: + # -- jans-scim.log target + scimLogTarget: "STDOUT" + # -- jans-scim.log level + scimLogLevel: "INFO" + # -- jans-scim_persistence.log target + persistenceLogTarget: "FILE" + # -- jans-scim_persistence.log level + persistenceLogLevel: "INFO" + # -- jans-scim_persistence_duration.log target + persistenceDurationLogTarget: "FILE" + # -- jans-scim_persistence_duration.log level + persistenceDurationLogLevel: "INFO" + # -- jans-scim_persistence_ldap_statistics.log target + ldapStatsLogTarget: "FILE" + # -- jans-scim_persistence_ldap_statistics.log level + ldapStatsLogLevel: "INFO" + # -- jans-scim_script.log target + scriptLogTarget: "FILE" + # -- jans-scim_script.log level + scriptLogLevel: "INFO" # -- StorageClass section for Jackrabbit and OpenDJ charts. This is not currently used by the openbanking distribution. You may specify custom parameters as needed. storageClass: allowVolumeExpansion: true @@ -602,65 +485,88 @@ global: provisioner: microk8s.io/hostpath reclaimPolicy: Retain volumeBindingMode: WaitForFirstConsumer + oxshibboleth: + # -- Boolean flag to enable/disable the oxShibbboleth chart. Not part of the openbanking distribution. Keep as default. + enabled: false + opendj: + # -- Boolean flag to enable/disable the OpenDJ chart. Not part of the openbanking distribution. Keep as default. + enabled: false + admin-ui: + # -- Boolean flag to enable/disable the admin-ui chart and admin ui config api plugin. + enabled: false upgrade: - # -- Boolean flag used when running helm upgrade command. This allows upgrading the chart without immutable objects errors. + # -- Boolean flag used when running upgrading through versions command. enabled: false # -- Nginx ingress definitions chart nginx-ingress: ingress: # -- Enable Admin UI endpoints. COMING SOON. - adminUiEnabled: true + adminUiEnabled: false # -- Admin UI ingress resource labels. key app is taken. adminUiLabels: { } + # -- openid-configuration ingress resource additional annotations. + adminUiAdditionalAnnotations: { } # -- Enable endpoint /.well-known/openid-configuration openidConfigEnabled: true # -- openid-configuration ingress resource labels. key app is taken openidConfigLabels: { } + # -- openid-configuration ingress resource additional annotations. + openidAdditionalAnnotations: { } # -- Enable endpoint /.well-known/uma2-configuration uma2ConfigEnabled: true # -- uma2 config ingress resource labels. key app is taken uma2ConfigLabels: { } + # -- uma2 config ingress resource additional annotations. + uma2AdditionalAnnotations: { } # -- Enable endpoint /.well-known/webfinger webfingerEnabled: true # -- webfinger ingress resource labels. key app is taken webfingerLabels: { } + # -- webfinger ingress resource additional annotations. + webfingerAdditionalAnnotations: { } # -- Enable endpoint /.well-known/simple-web-discovery webdiscoveryEnabled: true # -- webdiscovery ingress resource labels. key app is taken webdiscoveryLabels: { } - # -- Enable endpoint /.well-known/scim-configuration - scimConfigEnabled: false - # -- SCIM config ingress resource labels. key app is taken - scimConfigLabels: { } - # -- Enable SCIM endpoints /jans-scim - scimEnabled: false - # -- SCIM config ingress resource labels. key app is taken - scimLabels: { } + # -- webdiscovery ingress resource additional annotations. + webdiscoveryAdditionalAnnotations: { } # Enable config API endpoints /jans-config-api configApiEnabled: true # -- configAPI ingress resource labels. key app is taken configApiLabels: { } + # -- ConfigAPI ingress resource additional annotations. + configApiAdditionalAnnotations: { } # -- Enable endpoint /.well-known/fido-configuration u2fConfigEnabled: true # -- u2f config ingress resource labels. key app is taken u2fConfigLabels: { } + # -- u2f config ingress resource additional annotations. + u2fAdditionalAnnotations: { } # -- Enable endpoint /.well-known/fido2-configuration fido2ConfigEnabled: false # -- fido2 config ingress resource labels. key app is taken fido2ConfigLabels: { } + # -- fido2 config ingress resource additional annotations. + fido2ConfigAdditionalAnnotations: { } # -- Enable Auth server endpoints /jans-auth authServerEnabled: true - # -- Auth server config ingress resource labels. key app is taken + # -- Auth server ingress resource labels. key app is taken authServerLabels: { } + # -- Auth server ingress resource additional annotations. + authServerAdditionalAnnotations: { } # -- Enable mTLS on Auth server endpoint /jans-auth/restv1/token authServerProtectedToken: false # -- Auth server protected token ingress resource labels. key app is taken authServerProtectedTokenLabels: { } + # -- Auth server protected token ingress resource additional annotations. + authServerProtectedTokenAdditionalAnnotations: { } # -- Enable mTLS onn Auth server endpoint /jans-auth/restv1/register authServerProtectedRegister: false # -- Auth server protected token ingress resource labels. key app is taken - authServerProtectedRedisterLabels: { } + authServerProtectedRegisterLabels: { } + # -- Auth server protected register ingress resource additional annotations. + authServerProtectedRegisterAdditionalAnnotations: { } # -- Additional labels that will be added across all ingress definitions in the format of {mylabel: "myapp"} additionalLabels: { } # -- Additional annotations that will be added across all ingress definitions in the format of {cert-manager.io/issuer: "letsencrypt-prod"} @@ -682,124 +588,6 @@ nginx-ingress: hosts: - demoexample.gluu.org -# -- OpenDJ is a directory server which implements a wide range of Lightweight Directory Access Protocol and related standards, including full compliance with LDAPv3 but also support for Directory Service Markup Language (DSMLv2).Written in Java, OpenDJ offers multi-master replication, access control, and many extensions. -opendj: - # -- Configure the HorizontalPodAutoscaler - hpa: - enabled: true - minReplicas: 1 - maxReplicas: 10 - targetCPUUtilizationPercentage: 50 - # -- metrics if targetCPUUtilizationPercentage is not set - metrics: [] - # -- Scaling Policies - behavior: {} - # -- Add custom normal and secret envs to the service - usrEnvs: - # -- Add custom normal envs to the service - # variable1: value1 - normal: {} - # -- Add custom secret envs to the service - # variable1: value1 - secret: {} - # -- Add custom dns policy - dnsPolicy: "" - # -- Add custom dns config - dnsConfig: {} - image: - # -- Image pullPolicy to use for deploying. - pullPolicy: IfNotPresent - # -- Image to use for deploying. - repository: gluufederation/opendj - # -- Image tag to use for deploying. - tag: 5.0.0_dev - multiCluster: - # -- Enable OpenDJ multiCluster mode. This flag enabbles loading keys under `opendj.multiCluster` - enabled: false - # -- OpenDJ Serf advertise address for the cluster - serfAdvertiseAddr: "firstldap.gluu.org:30946" - # -- Serf key. This key will automatically sync across clusters. - serfKey: Z51b6PgKU1MZ75NCZOTGGoc0LP2OF3qvF6sjxHyQCYk= - # -- Serf peer addresses. One per cluster. - serfPeers: - - "firstldap.gluu.org:30946" - - "secondldap.gluu.org:31946" - persistence: - # -- OpenDJ volume size - size: 5Gi - ports: - tcp-admin: - nodePort: "" - port: 4444 - protocol: TCP - targetPort: 4444 - tcp-ldap: - nodePort: "" - port: 1389 - protocol: TCP - targetPort: 1389 - tcp-ldaps: - nodePort: "" - port: 1636 - protocol: TCP - targetPort: 1636 - tcp-repl: - nodePort: "" - port: 8989 - protocol: TCP - targetPort: 8989 - tcp-serf: - nodePort: "" - port: 7946 - protocol: TCP - targetPort: 7946 - udp-serf: - nodePort: "" - port: 7946 - protocol: UDP - targetPort: 7946 - # -- Service replica number. - replicas: 1 - # -- Resource specs. - resources: - limits: - # -- CPU limit. - cpu: 1500m - # -- Memory limit. - memory: 2000Mi - requests: - # -- CPU request. - cpu: 1500m - # -- Memory request. - memory: 2000Mi - # -- Configure the liveness healthcheck for OpenDJ if needed. - # https://github.com/GluuFederation/docker-opendj/blob/master/scripts/healthcheck.py - livenessProbe: - # -- Executes the python3 healthcheck. - exec: - command: - - python3 - - /app/scripts/healthcheck.py - initialDelaySeconds: 30 - periodSeconds: 30 - timeoutSeconds: 5 - failureThreshold: 20 - # -- Configure the readiness healthcheck for OpenDJ if needed. - # https://github.com/GluuFederation/docker-opendj/blob/master/scripts/healthcheck.py - readinessProbe: - exec: - command: - - python3 - - /app/scripts/healthcheck.py - initialDelaySeconds: 25 - timeoutSeconds: 5 - periodSeconds: 25 - failureThreshold: 20 - # -- Configure any additional volumes that need to be attached to the pod - volumes: [] - # -- Configure any additional volumesMounts that need to be attached to the containers - volumeMounts: [] - # -- Job to generate data and intial config for Gluu Server persistence layer. persistence: # -- Add custom normal and secret envs to the service @@ -820,7 +608,9 @@ persistence: # -- Image to use for deploying. repository: janssenproject/persistence-loader # -- Image tag to use for deploying. - tag: 1.0.0_b11 + tag: 1.0.0-beta.14 + # -- Image Pull Secrets + pullSecrets: [ ] # -- Resource specs. resources: limits: @@ -838,72 +628,7 @@ persistence: # -- Configure any additional volumesMounts that need to be attached to the containers volumeMounts: [] -# -- System for Cross-domain Identity Management (SCIM) version 2.0 -scim: - # -- Configure the HorizontalPodAutoscaler - hpa: - enabled: true - minReplicas: 1 - maxReplicas: 10 - targetCPUUtilizationPercentage: 50 - # -- metrics if targetCPUUtilizationPercentage is not set - metrics: [] - # -- Scaling Policies - behavior: {} - # -- Add custom normal and secret envs to the service - usrEnvs: - # -- Add custom normal envs to the service - # variable1: value1 - normal: {} - # -- Add custom secret envs to the service - # variable1: value1 - secret: {} - # -- Add custom dns policy - dnsPolicy: "" - # -- Add custom dns config - dnsConfig: {} - image: - # -- Image pullPolicy to use for deploying. - pullPolicy: IfNotPresent - # -- Image to use for deploying. - repository: janssenproject/scim - # -- Image tag to use for deploying. - tag: 1.0.0_b11 - # -- Service replica number. - replicas: 1 - resources: - limits: - # -- CPU limit. - cpu: 1000m - # -- Memory limit. - memory: 1000Mi - requests: - # -- CPU request. - cpu: 1000m - # -- Memory request. - memory: 1000Mi - service: - # -- Name of the auth-server service. Please keep it as default. - scimServiceName: scim - # -- Configure the liveness healthcheck for SCIM if needed. - livenessProbe: - httpGet: - # -- http liveness probe endpoint - path: /jans-scim/sys/health-check - port: 8080 - initialDelaySeconds: 30 - periodSeconds: 30 - timeoutSeconds: 5 - # -- Configure the readiness healthcheck for the SCIM if needed. - readinessProbe: - httpGet: - # -- http readiness probe endpoint - path: /jans-scim/sys/health-check - port: 8080 - initialDelaySeconds: 25 - periodSeconds: 25 - timeoutSeconds: 5 - # -- Configure any additional volumes that need to be attached to the pod - volumes: [] - # -- Configure any additional volumesMounts that need to be attached to the containers - volumeMounts: [] + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } diff --git a/charts/jans/templates/_helpers.tpl b/helm/pygluu/kubernetes/templates/helm/gluu/templates/_helpers.tpl similarity index 100% rename from charts/jans/templates/_helpers.tpl rename to helm/pygluu/kubernetes/templates/helm/gluu/templates/_helpers.tpl diff --git a/charts/jans/values.schema.json b/helm/pygluu/kubernetes/templates/helm/gluu/values.schema.json similarity index 86% rename from charts/jans/values.schema.json rename to helm/pygluu/kubernetes/templates/helm/gluu/values.schema.json index e9c10674f6b..610844ece0b 100644 --- a/charts/jans/values.schema.json +++ b/helm/pygluu/kubernetes/templates/helm/gluu/values.schema.json @@ -2,8 +2,15 @@ "$schema":"https://json-schema.org/draft/2020-12/schema#", "type":"object", "properties":{ + "admin-ui":{ + "description":"Admin GUI for configuration of the auth-server", + "type":"object", + "properties":{ + + } + }, "auth-server":{ - "description":"OAuth Authorization Server, the OpenID Connect Provider, the UMA Authorization Server--this is the main Internet facing component of Janssen. It's the service that returns tokens, JWT's and identity assertions. This service must be Internet facing.", + "description":"OAuth Authorization Server, the OpenID Connect Provider, the UMA Authorization Server--this is the main Internet facing component of Gluu. It's the service that returns tokens, JWT's and identity assertions. This service must be Internet facing.", "type":"object", "properties":{ @@ -17,7 +24,7 @@ } }, "casa":{ - "description":"Janssen Casa (\"Casa\") is a self-service web portal for end-users to manage authentication and authorization preferences for their account in a Janssen Server.", + "description":"Gluu Casa (\"Casa\") is a self-service web portal for end-users to manage authentication and authorization preferences for their account in a Gluu Server.", "type":"object", "properties":{ @@ -31,7 +38,7 @@ } }, "config":{ - "description":"Configuration parameters for setup and initial configuration secret annd config layers used by Janssen services.", + "description":"Configuration parameters for setup and initial configuration secret annd config layers used by Gluu services.", "type":"object", "properties":{ "adminPass":{ @@ -97,7 +104,7 @@ "pattern":"^(NATIVE_PERSISTENCE|REDIS|IN_MEMORY)$" }, "cnCasaEnabled":{ - "description":"Enable Casa. Janssen Casa is a self-service web portal for end-users to manage authentication and authorization preferences for their account in a Janssen Server.", + "description":"Enable Casa. Gluu Casa is a self-service web portal for end-users to manage authentication and authorization preferences for their account in a Gluu Server.", "type":"boolean" }, "cnClientApiAdminCertCn":{ @@ -120,7 +127,7 @@ "pattern":"^[a-z]+$" }, "cnCouchbaseBucketPrefix":{ - "description":"The prefix of couchbase buckets. This helps with separation in between different environments and allows for the same couchbase cluster to be used by different setups of Janssen.", + "description":"The prefix of couchbase buckets. This helps with separation in between different environments and allows for the same couchbase cluster to be used by different setups of Gluu.", "type":"string", "pattern":"^[a-z]+$" }, @@ -175,6 +182,53 @@ "type":"string", "pattern":"^(LOCAL|JCA)$" }, + "cnJackrabbitAdminId":{ + "description":"Jackrabbit admin uid.", + "type":"string", + "pattern":"^[a-z]+$" + }, + "cnJackrabbitAdminIdFile":{ + "description":"The location of the Jackrabbit admin uid config.cnJackrabbitAdminId. The file path must end with jackrabbit_admin_id.", + "type":"string", + "pattern":".*jackrabbit_admin_id\\b.*" + }, + "cnJackrabbitAdminPassFile":{ + "description":"The location of the Jackrabbit admin password jackrabbit.secrets.cnJackrabbitAdminPassword. The file path must end with jackrabbit_admin_password.", + "type":"string", + "pattern":".*jackrabbit_admin_password\\b.*" + }, + "cnJackrabbitPostgresDatabaseName":{ + "description":"Jackrabbit postgres database name.", + "type":"string", + "pattern":"^[a-z]+$" + }, + "cnJackrabbitPostgresHost":{ + "description":"Postgres url", + "$ref":"#/definitions/fqdn-pattern" + }, + "cnJackrabbitPostgresPasswordFile":{ + "description":"The location of the Jackrabbit postgres password file jackrabbit.secrets.cnJackrabbitPostgresPassword. The file path must end with postgres_password.", + "type":"string", + "pattern":".*postgres_password\\b.*" + }, + "cnJackrabbitPostgresPort":{ + "description":"Jackrabbit Postgres port", + "type":"integer" + }, + "cnJackrabbitPostgresUser":{ + "description":"Jackrabbit Postgres uid", + "type":"string", + "pattern":"^[a-z]+$" + }, + "cnJackrabbitSyncInterval":{ + "description":"Interval between files sync (default to 300 seconds).", + "type":"integer" + }, + "cnJackrabbitUrl":{ + "description":"Jackrabbit internal url. Normally left as default.", + "type":"string", + "pattern":"^(http:\/\/)?[a-z0-9-:]+$" + }, "cnGoogleSecretManagerServiceAccount":{ "description":"Service account with roles roles/secretmanager.admin base64 encoded string. This is used often inside the services to reach the configuration layer. Used only when global.configAdapterName and global.configSecretAdapter is set to google.", "type":"string", @@ -201,12 +255,12 @@ "pattern":"^([0-9]|latest)*$" }, "cnSecretGoogleSecretNamePrefix":{ - "description":"Prefix for Janssen secret in Google Secret Manager. Defaults to jans. If left jans-secret secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google.", + "description":"Prefix for Gluu secret in Google Secret Manager. Defaults to gluu. If left gluu-secret secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google.", "type":"string", "pattern":"^[a-z]+$" }, "cnGoogleSecretManagerPassPhrase":{ - "description":"Passphrase for Janssen secret in Google Secret Manager. This is used for encrypting and decrypting data from the Google Secret Manager. Used only when global.configAdapterName and global.configSecretAdapter is set to google.", + "description":"Passphrase for Gluu secret in Google Secret Manager. This is used for encrypting and decrypting data from the Google Secret Manager. Used only when global.configAdapterName and global.configSecretAdapter is set to google.", "$ref":"#/definitions/password" }, "cnConfigGoogleSecretVersionId":{ @@ -215,7 +269,7 @@ "pattern":"^([0-9]|latest)*$" }, "cnConfigGoogleSecretNamePrefix":{ - "description":"Prefix for Janssen configuration secret in Google Secret Manager. Defaults to jans. If left intact jans-configuration secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google.", + "description":"Prefix for Gluu configuration secret in Google Secret Manager. Defaults to gluu. If left intact gluu-configuration secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google.", "type":"string" }, "cnLdapUrl":{ @@ -403,7 +457,82 @@ "description":"Name of the auth-server service. Please keep it as default.", "type":"string", "pattern":"^[a-z0-9-]+$" + }, + "appLoggers":{ + "type":"object", + "properties":{ + "authLogTarget":{ + "description":"jans-auth.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "authLogLevel":{ + "description":"jans-auth.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + }, + "httpLogTarget":{ + "description":"http_request_response target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "httpLogLevel":{ + "description":"http_request_response level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + }, + "persistenceLogTarget":{ + "description":"jans-auth_persistence.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "persistenceLogLevel":{ + "description":"jans-auth_persistence.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + }, + "persistenceDurationLogTarget":{ + "description":"jans-auth_persistence_duration.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "persistenceDurationLogLevel":{ + "description":"jans-auth_persistence_duration.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + }, + "ldapStatsLogTarget":{ + "description":"jans-auth_persistence_ldap_statistics.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "ldapStatsLogLevel":{ + "description":"jans-auth_persistence_ldap_statistics.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + }, + "scriptLogTarget":{ + "description":"jans-auth_script.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "scriptLogLevel":{ + "description":"jans-auth_script.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + }, + "auditStatsLogTarget":{ + "description":"jans-auth_audit.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "auditStatsLogLevel":{ + "description":"jans-auth_audit.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" } + } + } } }, "auth-server-key-rotation":{ @@ -441,7 +570,22 @@ "enabled":{ "description":"Boolean flag to enable/disable the client-api chart.", "type":"boolean" + }, + "appLoggers":{ + "type":"object", + "properties":{ + "clientApiLogTarget":{ + "description":"client-api.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "clientApiLogLevel":{ + "description":"client-api.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + } } + } } }, "cloud":{ @@ -458,7 +602,7 @@ "type":"boolean" }, "cnPersistenceType":{ - "description":"Persistence backend to run Janssen with ldap|couchbase|hybrid|sql|spanner.", + "description":"Persistence backend to run Gluu with ldap|couchbase|hybrid|sql|spanner.", "type":"string", "pattern":"^(ldap|couchbase|hybrid|sql|spanner)$" }, @@ -523,12 +667,12 @@ } }, "configAdapterName":{ - "description":"The config backend adapter that will hold Janssen configuration layer. google|kubernetes", + "description":"The config backend adapter that will hold Gluu configuration layer. google|kubernetes", "type":"string", "pattern":"^(kubernetes|google)$" }, "configSecretAdapter":{ - "description":"The config backend adapter that will hold Janssen secret layer. google|kubernetes", + "description":"The config backend adapter that will hold Gluu secret layer. google|kubernetes", "type":"string", "pattern":"^(kubernetes|google)$" }, @@ -548,7 +692,22 @@ "enabled":{ "description":"Boolean flag to enable/disable the config-api chart.", "type":"boolean" + }, + "appLoggers":{ + "type":"object", + "properties":{ + "configApiLogTarget":{ + "description":"configapi.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "configApiLogLevel":{ + "description":"configapi.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + } } + } } }, "cr-rotate":{ @@ -561,16 +720,46 @@ } }, "fqdn":{ - "description":"Fully qualified domain name to be used for Janssen installation. This address will be used to reach Janssen services.", + "description":"Fully qualified domain name to be used for Gluu installation. This address will be used to reach Gluu services.", "$ref":"#/definitions/fqdn-pattern" }, "fido2":{ "type":"object", "properties":{ + "fido2ServiceName":{ + "description":"Name of the fido2 service. Please keep it as default.", + "type":"string", + "pattern":"^[a-z0-9-]+$" + }, "enabled":{ "description":"Boolean flag to enable/disable the fido2 chart.", "type":"boolean" + }, + "appLoggers":{ + "type":"object", + "properties":{ + "fido2LogTarget":{ + "description":"fido2.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "fido2LogLevel":{ + "description":"fido2.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + }, + "persistenceLogTarget":{ + "description":"fido2_persistence.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "persistenceLogLevel":{ + "description":"fido2_persistence.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + } } + } } }, "gcePdStorageType":{ @@ -586,11 +775,11 @@ "type":"object", "properties":{ "enabled":{ - "description":"Boolean flag that enables using istio side cars with Janssen services.", + "description":"Boolean flag that enables using istio side cars with Gluu services.", "type":"boolean" }, "ingress":{ - "description":"Boolean flag that enables using istio gateway for Janssen. This assumes istio ingress is installed and hence the LB is available.", + "description":"Boolean flag that enables using istio gateway for Gluu. This assumes istio ingress is installed and hence the LB is available.", "type":"boolean" }, "namespace":{ @@ -604,7 +793,7 @@ "type":"object", "properties":{ "enabled":{ - "description":"Boolean flag to enable/disable the jackrabbit chart. For more information on how it is used inside Janssen https://jans.io/4.2/installation-guide/install-kubernetes/#working-with-jackrabbit. ", + "description":"Boolean flag to enable/disable the jackrabbit chart. For more information on how it is used inside Gluu https://gluu.org/docs/gluu-server/4.2/installation-guide/install-kubernetes/#working-with-jackrabbit. ", "type":"boolean" }, "jackRabbitServiceName":{ @@ -650,7 +839,7 @@ } }, "distribution":{ - "description":"Janssen distributions supported are: default|openbanking.", + "description":"Gluu distributions supported are: default|openbanking.", "type":"string", "pattern":"^(default|openbanking)$" }, @@ -669,7 +858,67 @@ "enabled":{ "description":"Boolean flag to enable/disable the SCIM chart.", "type":"boolean" + }, + "scimServiceName":{ + "description":"Name of the scim service. Please keep it as default.", + "type":"string", + "pattern":"^[a-z0-9-]+$" + }, + "appLoggers":{ + "type":"object", + "properties":{ + "authLogTarget":{ + "description":"jans-scim.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "authLogLevel":{ + "description":"jans-scim.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + }, + "persistenceLogTarget":{ + "description":"jans-scim_persistence.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "persistenceLogLevel":{ + "description":"jans-scim_persistence.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + }, + "persistenceDurationLogTarget":{ + "description":"jans-scim_persistence_duration.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "persistenceDurationLogLevel":{ + "description":"jans-scim_persistence_duration.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + }, + "ldapStatsLogTarget":{ + "description":"jans-scim_persistence_ldap_statistics.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "ldapStatsLogLevel":{ + "description":"jans-scim_persistence_ldap_statistics.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + }, + "scriptLogTarget":{ + "description":"jans-scim_script.log target", + "type":"string", + "pattern":"^(STDOUT|FILE)$" + }, + "scriptLogLevel":{ + "description":"jans-scim_script.log level", + "type":"string", + "pattern":"^(FATAL|ERROR|WARN|INFO|DEBUG|TRACE)$" + } } + } } }, "storageClass":{ @@ -755,21 +1004,21 @@ } }, "oxpassport":{ - "description":"Janssen interface to Passport.js to support social login and inbound identity.", + "description":"Gluu interface to Passport.js to support social login and inbound identity.", "type":"object", "properties":{ } }, "oxshibboleth":{ - "description":"Shibboleth project for the Janssen Server's SAML IDP functionality.", + "description":"Shibboleth project for the Gluu Server's SAML IDP functionality.", "type":"object", "properties":{ } }, "persistence":{ - "description":"Job to generate data and intial config for Janssen Server persistence layer.", + "description":"Job to generate data and intial config for Gluu Server persistence layer.", "type":"object", "properties":{ @@ -784,6 +1033,9 @@ } }, "allOf":[ + { + "$ref":"#/definitions/admin-ui-enabled" + }, { "$ref":"#/definitions/auth-server-enabled" }, @@ -864,7 +1116,7 @@ { "type":"string", "errors":{ - "pattern":"Setting not FQDN structured. Please enter a FQDN with the format demoexample.jans.io" + "pattern":"Setting not FQDN structured. Please enter a FQDN with the format demoexample.gluu.org" } }, { @@ -903,6 +1155,146 @@ } ] }, + "admin-ui-enabled":{ + "if":{ + "properties":{ + "global":{ + "properties":{ + "admin-ui":{ + "properties":{ + "enabled":{ + "const":"true" + } + } + } + } + } + } + }, + "then":{ + "properties":{ + "admin-ui":{ + "required":[ + "image", + "replicas", + "resources" + ], + "properties":{ + "hpa":{ + "description":"Configure the HorizontalPodAutoscaler", + "type":"object", + "properties":{ + "enabled":{ + "type":"boolean" + }, + "minReplicas":{ + "type":"integer" + }, + "maxReplicas":{ + "type":"integer" + }, + "targetCPUUtilizationPercentage":{ + "type":"integer" + }, + "metrics":{ + "description":"metrics if targetCPUUtilizationPercentage is not set", + "type":"array" + }, + "behavior":{ + "description":"Scaling Policies", + "type":"object" + } + } + }, + "usrEnvs":{ + "description":"Add custom normal and secret envs to the service", + "type":"object", + "properties":{ + "normal":{ + "description":"Add custom normal envs to the service", + "type":"object" + }, + "secret":{ + "description":"Add custom secret envs to the service", + "type":"object" + } + } + }, + "dnsPolicy":{ + "description":"Add custom dns policy", + "type":"string", + "pattern":"^(Default|ClusterFirst|ClusterFirstWithHostNet|None|)$" + }, + "dnsConfig":{ + "description":"Add custom dns config", + "type":"object" + }, + "image":{ + "type":"object", + "properties":{ + "pullPolicy":{ + "description":"Image pullPolicy to use for deploying.", + "type":"string", + "pattern":"^(Always|Never|IfNotPresent)$" + }, + "repository":{ + "description":"Image to use for deploying", + "type":"string", + "pattern":"^[a-z0-9-_/]+$" + }, + "tag":{ + "description":"Image tag to use for deploying.", + "type":"string", + "pattern":"^[a-z0-9-_.]+$" + } + } + }, + "replicas":{ + "description":"Service replica number.", + "type":"integer" + }, + "resources":{ + "description":"Resource specs.", + "type":"object", + "properties":{ + "limits":{ + "type":"object", + "properties":{ + "cpu":{ + "description":"CPU limit.", + "type":"string", + "pattern":"^[0-9m]+$" + }, + "memory":{ + "description":"Memory limit.", + "type":"string", + "pattern":"^[0-9Mi]+$" + } + } + }, + "requests":{ + "type":"object", + "properties":{ + "cpu":{ + "description":"CPU request.", + "type":"string", + "pattern":"^[0-9m]+$" + }, + "memory":{ + "description":"Memory request.", + "type":"string", + "pattern":"^[0-9Mi]+$" + } + } + } + } + } + } + } + } + }, + "else":true + }, "auth-server-enabled":{ "if":{ "properties":{ diff --git a/helm/pygluu/kubernetes/templates/helm/gluu/values.yaml b/helm/pygluu/kubernetes/templates/helm/gluu/values.yaml new file mode 100644 index 00000000000..6e351d64625 --- /dev/null +++ b/helm/pygluu/kubernetes/templates/helm/gluu/values.yaml @@ -0,0 +1,1654 @@ +# -- Only used by the installer. These settings do not affect nor are used by the chart +installer-settings: + currentVersion: "" + upgrade: + targetVersion: "" + image: + repository: "" + tag: "" + acceptLicense: "" + namespace: "" + releaseName: "" + nginxIngress: + releaseName: "" + namespace: "" + nodes: + names: "" + zones: "" + ips: "" + images: + edit: "" + aws: + lbType: "" + arn: + enabled: "" + arnAcmCert: "" + vpcCidr: "0.0.0.0/0" + couchbase: + clusterName: "" + namespace: "" + lowResourceInstall: "" + install: "" + customFileOverride: "" + backup: + incrementalSchedule: "" + fullSchedule: "" + retentionTime: "" + storageSize: "" + # Couchbase cert related keys + subjectAlternativeName: "" + commonName: "" + # Couchbase cluster yaml generator keys + totalNumberOfExpectedUsers: "" + totalNumberOfExpectedTransactionsPerSec: "" + volumeType: "" + volumeProvisionStrategy: "" + ldap: + multiClusterIds: [] + subsequentCluster: "" + backup: + fullSchedule: "" + jackrabbit: + clusterMode: "" + postgres: + install: "" + namespace: "" + sql: + install: "" + namespace: "" + google: + useSecretManager: "" + redis: + install: "" + namespace: "" + openbanking: + hasCnObTransportTrustStore: false + cnObTransportTrustStoreP12password: "" + confirmSettings: false + +# -- Admin GUI for configuration of the auth-server +admin-ui: + # -- Configure the HorizontalPodAutoscaler + hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} + # -- Add custom normal and secret envs to the service + usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: gluufederation/admin-ui + # -- Image tag to use for deploying. + tag: 1.0.0-beta.14 + # -- Image Pull Secrets + pullSecrets: [ ] + # -- Service replica number. + replicas: 1 + # -- Resource specs. + resources: + limits: + # -- CPU limit. + cpu: 2500m + # -- Memory limit. + memory: 2500Mi + requests: + # -- CPU request. + cpu: 2500m + # -- Memory request. + memory: 2500Mi + # -- Configure the liveness healthcheck for the admin ui if needed. + livenessProbe: + tcpSocket: + port: 1636 + initialDelaySeconds: 60 + timeoutSeconds: 5 + periodSeconds: 25 + failureThreshold: 20 + # -- Configure the readiness healthcheck for the admin ui if needed. + readinessProbe: + tcpSocket: + port: 1636 + initialDelaySeconds: 60 + timeoutSeconds: 5 + periodSeconds: 25 + failureThreshold: 20 + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } +# -- OAuth Authorization Server, the OpenID Connect Provider, the UMA Authorization Server--this is the main Internet facing component of Gluu. It's the service that returns tokens, JWT's and identity assertions. This service must be Internet facing. +auth-server: + # -- Configure the HorizontalPodAutoscaler + hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} + # -- Add custom normal and secret envs to the service + usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: janssenproject/auth-server + # -- Image tag to use for deploying. + tag: 1.0.0-beta.14 + # -- Image Pull Secrets + pullSecrets: [ ] + # -- Service replica number. + replicas: 1 + # -- Resource specs. + resources: + limits: + # -- CPU limit. + cpu: 2500m + # -- Memory limit. + memory: 2500Mi + requests: + # -- CPU request. + cpu: 2500m + # -- Memory request. + memory: 2500Mi + # -- Configure the liveness healthcheck for the auth server if needed. + livenessProbe: + # -- Executes the python3 healthcheck. + # https://github.com/JanssenProject/docker-jans-auth-server/blob/master/scripts/healthcheck.py + exec: + command: + - python3 + - /app/scripts/healthcheck.py + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + # -- Configure the readiness healthcheck for the auth server if needed. + # https://github.com/JanssenProject/docker-jans-auth-server/blob/master/scripts/healthcheck.py + readinessProbe: + exec: + command: + - python3 + - /app/scripts/healthcheck.py + initialDelaySeconds: 25 + periodSeconds: 25 + timeoutSeconds: 5 + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } +# -- Responsible for regenerating auth-keys per x hours +auth-server-key-rotation: + # -- Add custom normal and secret envs to the service + usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: janssenproject/certmanager + # -- Image tag to use for deploying. + tag: 1.0.0-beta.14 + # -- Image Pull Secrets + pullSecrets: [ ] + # -- Auth server key rotation keys life in hours + keysLife: 48 + # -- Resource specs. + resources: + limits: + # -- CPU limit. + cpu: 300m + # -- Memory limit. + memory: 300Mi + requests: + # -- CPU request. + cpu: 300m + # -- Memory request. + memory: 300Mi + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } +# -- Gluu Casa ("Casa") is a self-service web portal for end-users to manage authentication and authorization preferences for their account in a Gluu Server. +casa: + # -- Configure the HorizontalPodAutoscaler + hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} + # -- Add custom normal and secret envs to the service + usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: gluufederation/casa + # -- Image tag to use for deploying. + tag: 5.0.0_dev + # -- Image Pull Secrets + pullSecrets: [ ] + # -- Service replica number. + replicas: 1 + # -- Resource specs. + resources: + limits: + # -- CPU limit. + cpu: 500m + # -- Memory limit. + memory: 500Mi + requests: + # -- CPU request. + cpu: 500m + # -- Memory request. + memory: 500Mi + # -- Configure the liveness healthcheck for casa if needed. + livenessProbe: + httpGet: + # -- http liveness probe endpoint + path: /casa/health-check + port: http-casa + initialDelaySeconds: 25 + periodSeconds: 25 + timeoutSeconds: 5 + # -- Configure the readiness healthcheck for the casa if needed. + readinessProbe: + httpGet: + # -- http readiness probe endpoint + path: /casa/health-check + port: http-casa + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } +# -- Middleware API to help application developers call an OAuth, OpenID or UMA server. You may wonder why this is necessary. It makes it easier for client developers to use OpenID signing and encryption features, without becoming crypto experts. This API provides some high level endpoints to do some of the heavy lifting. +client-api: + # -- Configure the HorizontalPodAutoscaler + hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} + # -- Add custom normal and secret envs to the service + usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: janssenproject/client-api + # -- Image tag to use for deploying. + tag: 1.0.0-beta.14 + # -- Image Pull Secrets + pullSecrets: [ ] + # -- Service replica number. + replicas: 1 + # -- Resource specs. + resources: + limits: + # -- CPU limit. + cpu: 1000m + # -- Memory limit. + memory: 400Mi + requests: + # -- CPU request. + cpu: 1000m + # -- Memory request. + memory: 400Mi + # -- Configure the liveness healthcheck for the auth server if needed. + livenessProbe: + # -- Executes the python3 healthcheck. + exec: + command: + - curl + - -k + - https://localhost:8443/health-check + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + # -- Configure the readiness healthcheck for the auth server if needed. + readinessProbe: + tcpSocket: + port: 8443 + initialDelaySeconds: 60 + timeoutSeconds: 5 + periodSeconds: 25 + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } +# -- Configuration parameters for setup and initial configuration secret and config layers used by Gluu services. +config: + # -- Add custom normal and secret envs to the service. + usrEnvs: + # -- Add custom normal envs to the service. + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service. + # variable1: value1 + secret: {} + # -- Admin password to log in to the UI. + adminPassword: Test1234# + # -- City. Used for certificate creation. + city: Austin + configmap: + # -- Jetty header size in bytes in the auth server + cnJettyRequestHeaderSize: 8192 + # -- SQL database dialect. `mysql` or `pgsql` + cnSqlDbDialect: mysql + # -- SQL database host uri. + cnSqlDbHost: my-release-mysql.default.svc.cluster.local + # -- SQL database port. + cnSqlDbPort: 3306 + # -- SQL database name. + cnSqlDbName: jans + # -- SQL database username. + cnSqlDbUser: jans + # -- SQL database timezone. + cnSqlDbTimezone: UTC + # -- SQL password file holding password from config.configmap.cnSqldbUserPassword . + cnSqlPasswordFile: /etc/jans/conf/sql_password + # -- SQL password injected as config.configmap.cnSqlPasswordFile . + cnSqldbUserPassword: Test1234# + # -- Cache type. `NATIVE_PERSISTENCE`, `REDIS`. or `IN_MEMORY`. Defaults to `NATIVE_PERSISTENCE` . + cnCacheType: NATIVE_PERSISTENCE + # -- Enable Casa flag . + cnCasaEnabled: false + # -- Client-api OAuth client admin certificate common name. This should be left to the default value client-api . + cnClientApiAdminCertCn: client-api + # -- Client-api OAuth client application certificate common name. This should be left to the default value client-api. + cnClientApiApplicationCertCn: client-api + # -- Client-api bind address. This limits what ip ranges can access the client-api. This should be left as * and controlled by a NetworkPolicy + cnClientApiBindIpAddresses: "*" + # -- The name of the Kubernetes ConfigMap that will hold the configuration layer + cnConfigKubernetesConfigMap: cn + # -- The prefix of couchbase buckets. This helps with separation in between different environments and allows for the same couchbase cluster to be used by different setups of Gluu. + cnCouchbaseBucketPrefix: jans + # -- Location of `couchbase.crt` used by Couchbase SDK for tls termination. The file path must end with couchbase.crt. In mTLS setups this is not required. + cnCouchbaseCertFile: /etc/certs/couchbase.crt + # -- Couchbase certificate authority string. This must be encoded using base64. This can also be found in your couchbase UI Security > Root Certificate. In mTLS setups this is not required. + cnCouchbaseCrt: SWFtTm90YVNlcnZpY2VBY2NvdW50Q2hhbmdlTWV0b09uZQo= + # -- The number of replicas per index created. Please note that the number of index nodes must be one greater than the number of index replicas. That means if your couchbase cluster only has 2 index nodes you cannot place the number of replicas to be higher than 1. + cnCouchbaseIndexNumReplica: 0 + # -- Couchbase password for the restricted user config.configmap.cnCouchbaseUser that is often used inside the services. The password must contain one digit, one uppercase letter, one lower case letter and one symbol . + cnCouchbasePassword: P@ssw0rd + # -- The location of the Couchbase restricted user config.configmap.cnCouchbaseUser password. The file path must end with couchbase_password + cnCouchbasePasswordFile: /etc/gluu/conf/couchbase_password + # -- The Couchbase super user (admin) user name. This user is used during initialization only. + cnCouchbaseSuperUser: admin + # -- Couchbase password for the super user config.configmap.cnCouchbaseSuperUser that is used during the initialization process. The password must contain one digit, one uppercase letter, one lower case letter and one symbol + cnCouchbaseSuperUserPassword: Test1234# + # -- The location of the Couchbase restricted user config.configmap.cnCouchbaseSuperUser password. The file path must end with couchbase_superuser_password. + cnCouchbaseSuperUserPasswordFile: /etc/gluu/conf/couchbase_superuser_password + # -- Couchbase URL. Used only when global.cnPersistenceType is hybrid or couchbase. This should be in FQDN format for either remote or local Couchbase clusters. The address can be an internal address inside the kubernetes cluster + cnCouchbaseUrl: cbgluu.default.svc.cluster.local + # -- Couchbase restricted user. Used only when global.cnPersistenceType is hybrid or couchbase. + cnCouchbaseUser: gluu + # -- Document store type to use for shibboleth files JCA or LOCAL. Note that if JCA is selected Apache Jackrabbit will be used. Jackrabbit also enables loading custom files across all services easily. + cnDocumentStoreType: JCA + # -- Jackrabbit admin uid. + cnJackrabbitAdminId: admin + # -- The location of the Jackrabbit admin uid config.cnJackrabbitAdminId. The file path must end with jackrabbit_admin_id. + cnJackrabbitAdminIdFile: /etc/gluu/conf/jackrabbit_admin_id + # -- The location of the Jackrabbit admin password jackrabbit.secrets.cnJackrabbitAdminPassword. The file path must end with jackrabbit_admin_password. + cnJackrabbitAdminPasswordFile: /etc/gluu/conf/jackrabbit_admin_password + # -- Jackrabbit postgres database name. + cnJackrabbitPostgresDatabaseName: jackrabbit + # -- Postgres url + cnJackrabbitPostgresHost: postgresql.postgres.svc.cluster.local + # -- The location of the Jackrabbit postgres password file jackrabbit.secrets.cnJackrabbitPostgresPassword. The file path must end with postgres_password. + cnJackrabbitPostgresPasswordFile: /etc/gluu/conf/postgres_password + # -- Jackrabbit Postgres port + cnJackrabbitPostgresPort: 5432 + # -- Jackrabbit Postgres uid + cnJackrabbitPostgresUser: jackrabbit + # -- Interval between files sync (default to 300 seconds). + cnJackrabbitSyncInterval: 300 + # -- Jackrabbit internal url. Normally left as default. + cnJackrabbitUrl: "http://jackrabbit:8080" + # [google_envs] Envs related to using Google + # -- Service account with roles roles/secretmanager.admin base64 encoded string. This is used often inside the services to reach the configuration layer. Used only when global.configAdapterName and global.configSecretAdapter is set to google. + cnGoogleSecretManagerServiceAccount: SWFtTm90YVNlcnZpY2VBY2NvdW50Q2hhbmdlTWV0b09uZQo= + # -- Project id of the google project the secret manager belongs to. Used only when global.configAdapterName and global.configSecretAdapter is set to google. + cnGoogleProjectId: google-project-to-save-config-and-secrets-to + # [google_spanner_envs] Envs related to using Google Secret Manager to store config and secret layer + # -- Google Spanner ID. Used only when global.cnPersistenceType is spanner. + cnGoogleSpannerInstanceId: "" + # -- Google Spanner Database ID. Used only when global.cnPersistenceType is spanner. + cnGoogleSpannerDatabaseId: "" + # [google_spanner_envs] END + # [google_secret_manager_envs] Envs related to using Google Secret Manager to store config and secret layer + # -- Secret version to be used for secret configuration. Defaults to latest and should normally always stay that way. Used only when global.configAdapterName and global.configSecretAdapter is set to google. + cnSecretGoogleSecretVersionId: "latest" + # -- Prefix for Gluu secret in Google Secret Manager. Defaults to gluu. If left gluu-secret secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google. + cnSecretGoogleSecretNamePrefix: gluu + # -- Passphrase for Gluu secret in Google Secret Manager. This is used for encrypting and decrypting data from the Google Secret Manager. Used only when global.configAdapterName and global.configSecretAdapter is set to google. + cnGoogleSecretManagerPassPhrase: Test1234# + # -- Secret version to be used for configuration. Defaults to latest and should normally always stay that way. Used only when global.configAdapterName and global.configSecretAdapter is set to google. Used only when global.configAdapterName and global.configSecretAdapter is set to google. + cnConfigGoogleSecretVersionId: "latest" + # -- Prefix for Gluu configuration secret in Google Secret Manager. Defaults to gluu. If left intact gluu-configuration secret will be created. Used only when global.configAdapterName and global.configSecretAdapter is set to google. + cnConfigGoogleSecretNamePrefix: gluu + # [google_secret_manager_envs] END + # [google_envs] END + # -- OpenDJ internal address. Leave as default. Used when `global.cnPersistenceType` is set to `ldap`. + cnLdapUrl: "opendj:1636" + # -- Value passed to Java option -XX:MaxRAMPercentage + cnMaxRamPercent: "75.0" + # -- SCIM protection mode OAUTH|TEST|UMA + cnScimProtectionMode: "OAUTH" + # -- Boolean flag to enable/disable passport chart + cnPassportEnabled: false + # -- Specify data that should be saved in LDAP (one of default, user, cache, site, token, or session; default to default). Note this environment only takes effect when `global.cnPersistenceType` is set to `hybrid`. + cnPersistenceLdapMapping: default + # -- Redis Sentinel Group. Often set when `config.configmap.cnRedisType` is set to `SENTINEL`. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. + cnRedisSentinelGroup: "" + # -- Redis SSL truststore. Optional. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. + cnRedisSslTruststore: "" + # -- Redis service type. `STANDALONE` or `CLUSTER`. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. + cnRedisType: STANDALONE + # -- Redis URL and port number :. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. + cnRedisUrl: "redis.redis.svc.cluster.local:6379" + # -- Boolean to use SSL in Redis. Can be used when `config.configmap.cnCacheType` is set to `REDIS`. + cnRedisUseSsl: false + # -- Enable SAML-related features; UI menu, etc. + cnSamlEnabled: false + # -- Kubernetes secret name holding configuration keys. Used when global.configSecretAdapter is set to kubernetes which is the default. + cnSecretKubernetesSecret: cn + # -- Loadbalancer address for AWS if the FQDN is not registered. + lbAddr: "" + # -- Country code. Used for certificate creation. + countryCode: US + # -- Email address of the administrator usually. Used for certificate creation. + email: support@gluu.org + image: + # -- Image to use for deploying. + repository: janssenproject/configurator + # -- Image tag to use for deploying. + tag: 1.0.0-beta.14 + # -- Image Pull Secrets + pullSecrets: [ ] + # -- LDAP admin password if OpennDJ is used for persistence. + ldapPassword: P@ssw0rds + # -- Organization name. Used for certificate creation. + orgName: Gluu + # -- Redis admin password if `config.configmap.cnCacheType` is set to `REDIS`. + redisPassword: P@assw0rd + # -- Resource specs. + resources: + limits: + # -- CPU limit. + cpu: 300m + # -- Memory limit. + memory: 300Mi + requests: + # -- CPU request. + cpu: 300m + # -- Memory request. + memory: 300Mi + # -- State code. Used for certificate creation. + state: TX + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + # -- CE to CN Migration section + migration: + # -- Boolean flag to enable migration from CE + enabled: false + # -- Directory holding all migration files + migrationDir: /ce-migration + # -- migration data-format depending on persistence backend. + # Supported data formats are ldif, couchbase+json, spanner+avro, postgresql+json, and mysql+json. + migrationDataFormat: ldif + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } +# -- Config Api endpoints can be used to configure the auth-server, which is an open-source OpenID Connect Provider (OP) and UMA Authorization Server (AS). +config-api: + # -- Configure the HorizontalPodAutoscaler + hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} + # -- Add custom normal and secret envs to the service + usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: janssenproject/config-api + # -- Image tag to use for deploying. + tag: 1.0.0-beta.14 + # -- Image Pull Secrets + pullSecrets: [ ] + # -- Service replica number. + replicas: 1 + # -- Resource specs. + resources: + limits: + # -- CPU limit. + cpu: 1000m + # -- Memory limit. + memory: 400Mi + requests: + # -- CPU request. + cpu: 1000m + # -- Memory request. + memory: 400Mi + # -- Configure the liveness healthcheck for the auth server if needed. + livenessProbe: + # -- http liveness probe endpoint + httpGet: + path: /jans-config-api/api/v1/health/live + port: 8074 + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + readinessProbe: + # -- http readiness probe endpoint + httpGet: + path: jans-config-api/api/v1/health/ready + port: 8074 + initialDelaySeconds: 25 + periodSeconds: 25 + timeoutSeconds: 5 + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } +# -- CacheRefreshRotation is a special container to monitor cache refresh on oxTrust containers. This may be depreciated. +cr-rotate: + # -- Add custom normal and secret envs to the service + usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: gluufederation/cr-rotate + # -- Image tag to use for deploying. + tag: 5.0.0_dev + # -- Image Pull Secrets + pullSecrets: [ ] + # -- Resource specs. + resources: + limits: + # -- CPU limit. + cpu: 200m + # -- Memory limit. + memory: 200Mi + requests: + # -- CPU request. + cpu: 200m + # -- Memory request. + memory: 200Mi + service: + # -- Name of the cr-rotate service. Please keep it as default. + crRotateServiceName: cr-rotate + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } +# -- FIDO 2.0 (FIDO2) is an open authentication standard that enables leveraging common devices to authenticate to online services in both mobile and desktop environments. +fido2: + # -- Configure the HorizontalPodAutoscaler + hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} + # -- Add custom normal and secret envs to the service + usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: janssenproject/fido2 + # -- Image tag to use for deploying. + tag: 1.0.0-beta.14 + # -- Image Pull Secrets + pullSecrets: [ ] + # -- Service replica number. + replicas: 1 + # -- Resource specs. + resources: + limits: + # -- CPU limit. + cpu: 500m + # -- Memory limit. + memory: 500Mi + requests: + # -- CPU request. + cpu: 500m + # -- Memory request. + memory: 500Mi + service: + # -- The name of the fido2 port within the fido2 service. Please keep it as default. + name: http-fido2 + # -- Port of the fido2 service. Please keep it as default. + port: 8080 + # -- Configure the liveness healthcheck for the fido2 if needed. + livenessProbe: + # -- http liveness probe endpoint + httpGet: + path: /jans-fido2/sys/health-check + port: http-fido2 + initialDelaySeconds: 25 + periodSeconds: 25 + timeoutSeconds: 5 + # -- Configure the readiness healthcheck for the fido2 if needed. + readinessProbe: + httpGet: + path: /jans-fido2/sys/health-check + port: http-fido2 + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } +# -- Parameters used globally across all services helm charts. +global: + # -- Add custom normal and secret envs to the service. + # Envs defined in global.userEnvs will be globally available to all services + usrEnvs: + # -- Add custom normal envs to the service. + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service. + # variable1: value1 + secret: {} + alb: + # -- Activates ALB ingress + ingress: false + + admin-ui: + # -- Boolean flag to enable/disable the admin-ui chart and admin ui config api plugin. + enabled: false + # -- Name of the admin-ui service. Please keep it as default. + adminUiServiceName: admin-ui + # License parameters + # -- Admin UI license API key. + adminUiApiKey: xxxxxxxxxxx + # -- Admin UI license API key mount location. + adminUiApiKeyFile: /etc/jans/conf/admin_ui_api_key + # -- Admin UI license product code. + adminUiProductCode: xxxxxxxxxxx + # -- Admin UI license product code mount location. + adminUiProductCodeFile: /etc/jans/conf/admin_ui_product_code + # -- Admin UI license shared key. + adminUiSharedKey: xxxxxxxxxxx + # -- Admin UI license shared key mount location. + adminUiSharedKeyFile: /etc/jans/conf/admin_ui_shared_key + # -- Admin UI license management key. + adminUiManagementKey: xxxxxxxxxxx + # -- Admin UI license management key mount location. + adminUiManagementKeyFile: /etc/jans/conf/admin_ui_management_key + + auth-server: + # -- Name of the auth-server service. Please keep it as default. + authServerServiceName: auth-server + # -- Boolean flag to enable/disable auth-server chart. You should never set this to false. + enabled: true + # -- App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. + appLoggers: + # -- jans-auth.log target + authLogTarget: "STDOUT" + # -- jans-auth.log level + authLogLevel: "INFO" + # -- http_request_response.log target + httpLogTarget: "FILE" + # -- http_request_response.log level + httpLogLevel: "INFO" + # -- jans-auth_persistence.log target + persistenceLogTarget: "FILE" + # -- jans-auth_persistence.log level + persistenceLogLevel: "INFO" + # -- jans-auth_persistence_duration.log target + persistenceDurationLogTarget: "FILE" + # -- jans-auth_persistence_duration.log level + persistenceDurationLogLevel: "INFO" + # -- jans-auth_persistence_ldap_statistics.log target + ldapStatsLogTarget: "FILE" + # -- jans-auth_persistence_ldap_statistics.log level + ldapStatsLogLevel: "INFO" + # -- jans-auth_script.log target + scriptLogTarget: "FILE" + # -- jans-auth_script.log level + scriptLogLevel: "INFO" + # -- jans-auth_script.log target + auditStatsLogTarget: "FILE" + # -- jans-auth_audit.log level + auditStatsLogLevel: "INFO" + # -- space-separated key algorithm for signing (default to `RS256 RS384 RS512 ES256 ES384 ES512 PS256 PS384 PS512`) + authSigKeys: "RS256 RS384 RS512 ES256 ES384 ES512 PS256 PS384 PS512" + # -- space-separated key algorithm for encryption (default to `RSA1_5 RSA-OAEP`) + authEncKeys: "RSA1_5 RSA-OAEP" + + auth-server-key-rotation: + # -- Boolean flag to enable/disable the auth-server-key rotation cronjob chart. + enabled: false + # -- Volume storage type if using AWS volumes. + awsStorageType: io1 + # -- Volume storage type if using Azure disks. + azureStorageAccountType: Standard_LRS + # -- Azure storage kind if using Azure disks + azureStorageKind: Managed + casa: + # -- Name of the casa service. Please keep it as default. + casaServiceName: casa + client-api: + # -- Name of the client-api service. Please keep it as default. + clientApiServerServiceName: client-api + # -- Boolean flag to enable/disable the client-api chart. + enabled: false + # -- App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. + appLoggers: + # -- client-api.log target + clientApiLogTarget: "STDOUT" + # -- client-api.log level + clientApiLogLevel: "INFO" + cloud: + # -- Boolean flag if enabled will strip resources requests and limits from all services. + testEnviroment: false + # -- Boolean flag if enabled will enable jackrabbit in cluster mode with Postgres. + cnJackrabbitCluster: false + # -- Persistence backend to run Gluu with ldap|couchbase|hybrid|sql|spanner. + cnPersistenceType: sql + # -- Open banking external signing jwks uri. Used in SSA Validation. + cnObExtSigningJwksUri: "" + # -- Open banking external signing jwks AS certificate authority string. Used in SSA Validation. This must be encoded using base64.. Used when `.global.cnObExtSigningJwksUri` is set. + cnObExtSigningJwksCrt: "" + # -- Open banking external signing jwks AS key string. Used in SSA Validation. This must be encoded using base64. Used when `.global.cnObExtSigningJwksUri` is set. + cnObExtSigningJwksKey: "" + # -- Open banking external signing jwks AS key passphrase to unlock provided key. This must be encoded using base64. Used when `.global.cnObExtSigningJwksUri` is set. + cnObExtSigningJwksKeyPassPhrase: "" + # -- Open banking external signing AS Alias. This is a kid value.Used in SSA Validation, kid used while encoding a JWT sent to token URL i.e XkwIzWy44xWSlcWnMiEc8iq9s2G + cnObExtSigningAlias: "" + # -- Open banking signing AS kid to force the AS to use a specific signing key. i.e Wy44xWSlcWnMiEc8iq9s2G + cnObStaticSigningKeyKid: "" + # -- Open banking AS transport crt. Used in SSA Validation. This must be encoded using base64. + cnObTransportCrt: "" + # -- Open banking AS transport key. Used in SSA Validation. This must be encoded using base64. + cnObTransportKey: "" + # -- Open banking AS transport key pas`sphrase to unlock AS transport key. This must be encoded using base64. + cnObTransportKeyPassPhrase: "" + # -- Open banking transport Alias used inside the JVM. + cnObTransportAlias: "" + # -- Open banking AS transport truststore crt. This is normally generated from the OB issuing CA, OB Root CA and Signing CA. Used when .global.cnObExtSigningJwksUri is set. Used in SSA Validation. This must be encoded using base64. + cnObTransportTrustStore: "" + config: + # -- Boolean flag to enable/disable the configuration chart. This normally should never be false + enabled: true + # -- The config backend adapter that will hold Gluu configuration layer. google|kubernetes + configAdapterName: kubernetes + # -- The config backend adapter that will hold Gluu secret layer. google|kubernetes + configSecretAdapter: kubernetes + # -- Base64 encoded service account. The sa must have roles/secretmanager.admin to use Google secrets and roles/spanner.databaseUser to use Spanner. + cnGoogleApplicationCredentials: /etc/jans/conf/google-credentials.json + config-api: + # -- Name of the config-api service. Please keep it as default. + configApiServerServiceName: config-api + # -- Boolean flag to enable/disable the config-api chart. + enabled: true + # -- App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. + appLoggers: + # -- configapi.log target + configApiLogTarget: "STDOUT" + # -- configapi.log level + configApiLogLevel: "INFO" + cr-rotate: + # -- Boolean flag to enable/disable the cr-rotate chart. + enabled: false + # -- Fully qualified domain name to be used for Gluu installation. This address will be used to reach Gluu services. + fqdn: demoexample.gluu.org + fido2: + # -- Name of the fido2 service. Please keep it as default. + fido2ServiceName: fido2 + # -- Boolean flag to enable/disable the fido2 chart. + enabled: true + # -- App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. + appLoggers: + # -- fido2.log target + fido2LogTarget: "STDOUT" + # -- fido2.log level + fido2LogLevel: "INFO" + # -- fido2_persistence.log target + persistenceLogTarget: "FILE" + # -- fido2_persistence.log level + persistenceLogLevel: "INFO" + # -- GCE storage kind if using Google disks + gcePdStorageType: pd-standard + # -- Boolean flag to enable mapping global.lbIp to global.fqdn inside pods on clouds that provide static ip for loadbalancers. On cloud that provide only addresses to the LB this flag will enable a script to actively scan config.configmap.lbAddr and update the hosts file inside the pods automatically. + isFqdnRegistered: false + istio: + # -- Boolean flag that enables using istio side cars with Gluu services. + enabled: false + # -- Boolean flag that enables using istio gateway for Gluu. This assumes istio ingress is installed and hence the LB is available. + ingress: false + # -- The namespace istio is deployed in. The is normally istio-system. + namespace: istio-system + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } + jackrabbit: + # -- Boolean flag to enable/disable the jackrabbit chart. For more information on how it is used inside Gluu https://gluu.org/docs/gluu-server/4.2/installation-guide/install-kubernetes/#working-with-jackrabbit. If disabled oxShibboleth cannot be run. + enabled: false + # -- Name of the Jackrabbit service. Please keep it as default. + jackRabbitServiceName: jackrabbit + # -- The Loadbalancer IP created by nginx or istio on clouds that provide static IPs. This is not needed if `global.fqdn` is globally resolvable. + lbIp: 22.22.22.22 + nginx-ingress: + # -- Boolean flag to enable/disable the nginx-ingress definitions chart. + enabled: true + opendj: + # -- Boolean flag to enable/disable the OpenDJ chart. + enabled: false + # -- Name of the OpenDJ service. Please keep it as default. + ldapServiceName: opendj + oxpassport: + # -- Name of the oxPassport service. Please keep it as default. + oxPassportServiceName: oxpassport + oxshibboleth: + # -- Name of the oxShibboleth service. Please keep it as default. + oxShibbolethServiceName: oxshibboleth + # -- Boolean flag to enable/disable the oxShibbboleth chart. + enabled: false + # -- Gluu distributions supported are: default|openbanking. + distribution: default + persistence: + # -- Boolean flag to enable/disable the persistence chart. + enabled: true + scim: + # -- Name of the scim service. Please keep it as default. + scimServiceName: scim + # -- Boolean flag to enable/disable the SCIM chart. + enabled: true + # -- App loggers can be configured to define where the logs will be redirected to and the level of each in which it should be displayed. + appLoggers: + # -- jans-scim.log target + scimLogTarget: "STDOUT" + # -- jans-scim.log level + scimLogLevel: "INFO" + # -- jans-scim_persistence.log target + persistenceLogTarget: "FILE" + # -- jans-scim_persistence.log level + persistenceLogLevel: "INFO" + # -- jans-scim_persistence_duration.log target + persistenceDurationLogTarget: "FILE" + # -- jans-scim_persistence_duration.log level + persistenceDurationLogLevel: "INFO" + # -- jans-scim_persistence_ldap_statistics.log target + ldapStatsLogTarget: "FILE" + # -- jans-scim_persistence_ldap_statistics.log level + ldapStatsLogLevel: "INFO" + # -- jans-scim_script.log target + scriptLogTarget: "FILE" + # -- jans-scim_script.log level + scriptLogLevel: "INFO" + # -- StorageClass section for Jackrabbit and OpenDJ charts. This is not currently used by the openbanking distribution. You may specify custom parameters as needed. + storageClass: + allowVolumeExpansion: true + allowedTopologies: [] + mountOptions: + - debug + # -- parameters: + #fsType: "" + #kind: "" + #pool: "" + #storageAccountType: "" + #type: "" + parameters: {} + provisioner: microk8s.io/hostpath + reclaimPolicy: Retain + volumeBindingMode: WaitForFirstConsumer + upgrade: + # -- Boolean flag used when running upgrading through versions command. Used when upgrading with LDAP as the persistence to load the 101x ldif. + enabled: false + +# -- Jackrabbit Oak is a complementary implementation of the JCR specification. It is an effort to implement a scalable and performant hierarchical content repository for use as the foundation of modern world-class web sites and other demanding content applications +# https://jackrabbit.apache.org/jcr/index.html +jackrabbit: + # -- Configure the HorizontalPodAutoscaler + hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} + # -- Add custom normal and secret envs to the service + usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: gluufederation/jackrabbit + # -- Image tag to use for deploying. + tag: 5.0.0_dev + # -- Image Pull Secrets + pullSecrets: [ ] + # -- Service replica number. + replicas: 1 + # -- Resource specs. + resources: + limits: + # -- CPU limit. + cpu: 1500m + # -- Memory limit. + memory: 1000Mi + requests: + # -- CPU request. + cpu: 1500m + # -- Memory request. + memory: 1000Mi + secrets: + # -- Jackrabbit admin uid password + cnJackrabbitAdminPassword: Test1234# + # -- Jackrabbit Postgres uid password + cnJackrabbitPostgresPassword: P@ssw0rd + storage: + # -- Jackrabbit volume size + size: 5Gi + # -- Configure the liveness healthcheck for the Jackrabbit if needed. + livenessProbe: + # -- Executes tcp healthcheck. + tcpSocket: + port: http-jackrabbit + initialDelaySeconds: 25 + periodSeconds: 25 + timeoutSeconds: 5 + # -- Configure the readiness healthcheck for the Jackrabbit if needed. + readinessProbe: + # -- Executes tcp healthcheck. + tcpSocket: + port: http-jackrabbit + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } +# -- Nginx ingress definitions chart +nginx-ingress: + ingress: + # -- Enable Admin UI endpoints. COMING SOON. + adminUiEnabled: false + # -- Admin UI ingress resource labels. key app is taken. + adminUiLabels: { } + # -- openid-configuration ingress resource additional annotations. + adminUiAdditionalAnnotations: { } + # -- Enable endpoint /.well-known/openid-configuration + openidConfigEnabled: true + # -- openid-configuration ingress resource labels. key app is taken + openidConfigLabels: { } + # -- openid-configuration ingress resource additional annotations. + openidAdditionalAnnotations: { } + # -- Enable endpoint /.well-known/uma2-configuration + uma2ConfigEnabled: true + # -- uma2 config ingress resource labels. key app is taken + uma2ConfigLabels: { } + # -- uma2 config ingress resource additional annotations. + uma2AdditionalAnnotations: { } + # -- Enable endpoint /.well-known/webfinger + webfingerEnabled: true + # -- webfinger ingress resource labels. key app is taken + webfingerLabels: { } + # -- webfinger ingress resource additional annotations. + webfingerAdditionalAnnotations: { } + # -- Enable endpoint /.well-known/simple-web-discovery + webdiscoveryEnabled: true + # -- webdiscovery ingress resource labels. key app is taken + webdiscoveryLabels: { } + # -- webdiscovery ingress resource additional annotations. + webdiscoveryAdditionalAnnotations: { } + # -- Enable endpoint /.well-known/scim-configuration + scimConfigEnabled: false + # -- SCIM config ingress resource labels. key app is taken + scimConfigLabels: { } + # -- SCIM config ingress resource additional annotations. + scimConfigAdditionalAnnotations: { } + # -- Enable SCIM endpoints /jans-scim + scimEnabled: false + # -- SCIM config ingress resource labels. key app is taken + scimLabels: { } + # -- SCIM ingress resource additional annotations. + scimAdditionalAnnotations: { } + # Enable config API endpoints /jans-config-api + configApiEnabled: true + # -- configAPI ingress resource labels. key app is taken + configApiLabels: { } + # -- ConfigAPI ingress resource additional annotations. + configApiAdditionalAnnotations: { } + # -- Enable endpoint /.well-known/fido-configuration + u2fConfigEnabled: true + # -- u2f config ingress resource labels. key app is taken + u2fConfigLabels: { } + # -- u2f config ingress resource additional annotations. + u2fAdditionalAnnotations: { } + # -- Enable endpoint /.well-known/fido2-configuration + fido2ConfigEnabled: false + # -- fido2 config ingress resource labels. key app is taken + fido2ConfigLabels: { } + # -- fido2 config ingress resource additional annotations. + fido2ConfigAdditionalAnnotations: { } + # -- Enable Auth server endpoints /jans-auth + authServerEnabled: true + # -- Auth server ingress resource labels. key app is taken + authServerLabels: { } + # -- Auth server ingress resource additional annotations. + authServerAdditionalAnnotations: { } + # -- Enable mTLS on Auth server endpoint /jans-auth/restv1/token + authServerProtectedToken: false + # -- Auth server protected token ingress resource labels. key app is taken + authServerProtectedTokenLabels: { } + # -- Auth server protected token ingress resource additional annotations. + authServerProtectedTokenAdditionalAnnotations: { } + # -- Enable mTLS onn Auth server endpoint /jans-auth/restv1/register + authServerProtectedRegister: false + # -- Auth server protected token ingress resource labels. key app is taken + authServerProtectedRegisterLabels: { } + # -- Auth server protected register ingress resource additional annotations. + authServerProtectedRegisterAdditionalAnnotations: { } + # -- Additional labels that will be added across all ingress definitions in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across all ingress definitions in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + # Enable client certificate authentication + # nginx.ingress.kubernetes.io/auth-tls-verify-client: "optional" + # Create the secret containing the trusted ca certificates + # nginx.ingress.kubernetes.io/auth-tls-secret: "gluu/tls-certificate" + # Specify the verification depth in the client certificates chain + # nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1" + # Specify if certificates are passed to upstream server + # nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true" + additionalAnnotations: {} + path: / + hosts: + - demoexample.gluu.org + # -- Secrets holding HTTPS CA cert and key. + tls: + - secretName: tls-certificate + hosts: + - demoexample.gluu.org + +# -- OpenDJ is a directory server which implements a wide range of Lightweight Directory Access Protocol and related standards, including full compliance with LDAPv3 but also support for Directory Service Markup Language (DSMLv2).Written in Java, OpenDJ offers multi-master replication, access control, and many extensions. +opendj: + # -- Configure ldap backup cronjob + backup: + enabled: true + cronJobSchedule: "*/59 * * * *" + # -- Configure the HorizontalPodAutoscaler + hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} + # -- Add custom normal and secret envs to the service + usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: gluufederation/opendj + # -- Image tag to use for deploying. + tag: 5.0.0_dev + # -- Image Pull Secrets + pullSecrets: [ ] + multiCluster: + # -- Enable OpenDJ multiCluster mode. This flag enables loading keys under `opendj.multiCluster` + enabled: false + # -- OpenDJ Serf advertise address suffix that will be added to each opendj replica. + # i.e RELEASE-NAME-opendj-regional-{{statefulset pod number}}-{{ $.Values.multiCluster.serfAdvertiseAddrSuffix }} + serfAdvertiseAddrSuffix: "regional.gluu.org:30946" + # -- Serf key. This key will automatically sync across clusters. + serfKey: Z51b6PgKU1MZ75NCZOTGGoc0LP2OF3qvF6sjxHyQCYk= + # -- Serf peer addresses. One per cluster. + serfPeers: + - "gluu-opendj-regional-0-regional.gluu.org:30946" + - "gluu-opendj-regional-0-regional.gluu.org:31946" + # -- The number of opendj non scalabble statefulsets to create. Each pod created must be resolvable as it follows + # the patterm RELEASE-NAME-opendj-regional-{{statefulset pod number}}-{{ $.Values.multiCluster.serfAdvertiseAddrSuffix }} + # If set to 1, with a release name of gluu, the address of the pod would be gluu-opendj-regional-0-regional.gluu.org + replicaCount: 1 + # -- This id needs to be unique to each kubernetes cluster in a multi cluster setup + # west, east, south, north, region ...etc If left empty it will be randomly generated. + clusterId: "" + # -- Namespace int id. This id needs to be a unique number 0-9 per gluu installation per namespace. + # Used when gluu is installed in the same kubernetes cluster more than once. + namespaceIntId: 0 + + persistence: + # -- OpenDJ volume size + size: 5Gi + ports: + tcp-admin: + nodePort: "" + port: 4444 + protocol: TCP + targetPort: 4444 + tcp-ldap: + nodePort: "" + port: 1389 + protocol: TCP + targetPort: 1389 + tcp-ldaps: + nodePort: "" + port: 1636 + protocol: TCP + targetPort: 1636 + tcp-repl: + nodePort: "" + port: 8989 + protocol: TCP + targetPort: 8989 + tcp-serf: + nodePort: "" + port: 7946 + protocol: TCP + targetPort: 7946 + udp-serf: + nodePort: "" + port: 7946 + protocol: UDP + targetPort: 7946 + # -- Service replica number. + replicas: 1 + # -- Resource specs. + resources: + limits: + # -- CPU limit. + cpu: 1500m + # -- Memory limit. + memory: 2000Mi + requests: + # -- CPU request. + cpu: 1500m + # -- Memory request. + memory: 2000Mi + # -- Configure the liveness healthcheck for OpenDJ if needed. + # https://github.com/GluuFederation/docker-opendj/blob/master/scripts/healthcheck.py + livenessProbe: + # -- Executes the python3 healthcheck. + exec: + command: + - python3 + - /app/scripts/healthcheck.py + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 20 + # -- Configure the readiness healthcheck for OpenDJ if needed. + # https://github.com/GluuFederation/docker-opendj/blob/master/scripts/healthcheck.py + readinessProbe: + tcpSocket: + port: 1636 + initialDelaySeconds: 60 + timeoutSeconds: 5 + periodSeconds: 25 + failureThreshold: 20 + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } +# -- Gluu interface to Passport.js to support social login and inbound identity. +oxpassport: + # -- Configure the HorizontalPodAutoscaler + hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} + # -- Add custom normal and secret envs to the service + usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: gluufederation/oxpassport + # -- Image tag to use for deploying. + tag: 5.0.0_dev + # -- Image Pull Secrets + pullSecrets: [ ] + # -- Service replica number + replicas: 1 + # -- Resource specs. + resources: + limits: + # -- CPU limit. + cpu: 700m + # -- Memory limit. + memory: 900Mi + requests: + # -- CPU request. + cpu: 700m + # -- Memory request. + memory: 900Mi + # -- Configure the liveness healthcheck for oxPassport if needed. + livenessProbe: + httpGet: + # -- http liveness probe endpoint + path: /passport/health-check + port: http-passport + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 20 + # -- Configure the readiness healthcheck for the oxPassport if needed. + readinessProbe: + httpGet: + # -- http readiness probe endpoint + path: /passport/health-check + port: http-passport + initialDelaySeconds: 25 + periodSeconds: 25 + timeoutSeconds: 5 + failureThreshold: 20 + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } +# -- Shibboleth project for the Gluu Server's SAML IDP functionality. +oxshibboleth: + # -- Configure the HorizontalPodAutoscaler + hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} + # -- Add custom normal and secret envs to the service + usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: gluufederation/oxshibboleth + # -- Image tag to use for deploying. + tag: 5.0.0_dev + # -- Image Pull Secrets + pullSecrets: [ ] + # -- Service replica number. + replicas: 1 + # -- Resource specs. + resources: + limits: + # -- CPU limit. + cpu: 1000m + # -- Memory limit. + memory: 1000Mi + requests: + # -- CPU request. + cpu: 1000m + # -- Memory request. + memory: 1000Mi + # -- Configure the liveness healthcheck for the oxShibboleth if needed. + livenessProbe: + httpGet: + # -- http liveness probe endpoint + path: /idp + port: http-oxshib + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + # -- Configure the readiness healthcheck for the casa if needed. + readinessProbe: + httpGet: + # -- http liveness probe endpoint + path: /idp + port: http-oxshib + initialDelaySeconds: 25 + periodSeconds: 25 + timeoutSeconds: 5 + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } +# -- Job to generate data and intial config for Gluu Server persistence layer. +persistence: + # -- Add custom normal and secret envs to the service + usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: janssenproject/persistence-loader + # -- Image tag to use for deploying. + tag: 1.0.0-beta.14 + # -- Image Pull Secrets + pullSecrets: [ ] + # -- Resource specs. + resources: + limits: + # -- CPU limit + cpu: 300m + # -- Memory limit. + memory: 300Mi + requests: + # -- CPU request. + cpu: 300m + # -- Memory request. + memory: 300Mi + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } +# -- System for Cross-domain Identity Management (SCIM) version 2.0 +scim: + # -- Configure the HorizontalPodAutoscaler + hpa: + enabled: true + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 50 + # -- metrics if targetCPUUtilizationPercentage is not set + metrics: [] + # -- Scaling Policies + behavior: {} + # -- Add custom normal and secret envs to the service + usrEnvs: + # -- Add custom normal envs to the service + # variable1: value1 + normal: {} + # -- Add custom secret envs to the service + # variable1: value1 + secret: {} + # -- Add custom dns policy + dnsPolicy: "" + # -- Add custom dns config + dnsConfig: {} + image: + # -- Image pullPolicy to use for deploying. + pullPolicy: IfNotPresent + # -- Image to use for deploying. + repository: janssenproject/scim + # -- Image tag to use for deploying. + tag: 1.0.0-beta.14 + # -- Image Pull Secrets + pullSecrets: [ ] + # -- Service replica number. + replicas: 1 + resources: + limits: + # -- CPU limit. + cpu: 1000m + # -- Memory limit. + memory: 1000Mi + requests: + # -- CPU request. + cpu: 1000m + # -- Memory request. + memory: 1000Mi + service: + # -- The name of the scim port within the scim service. Please keep it as default. + name: http-scim + # -- Port of the scim service. Please keep it as default. + port: 8080 + # -- Configure the liveness healthcheck for SCIM if needed. + livenessProbe: + httpGet: + # -- http liveness probe endpoint + path: /jans-scim/sys/health-check + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + # -- Configure the readiness healthcheck for the SCIM if needed. + readinessProbe: + httpGet: + # -- http readiness probe endpoint + path: /jans-scim/sys/health-check + port: 8080 + initialDelaySeconds: 25 + periodSeconds: 25 + timeoutSeconds: 5 + # -- Configure any additional volumes that need to be attached to the pod + volumes: [] + # -- Configure any additional volumesMounts that need to be attached to the containers + volumeMounts: [] + + # -- Additional labels that will be added across the gateway in the format of {mylabel: "myapp"} + additionalLabels: { } + # -- Additional annotations that will be added across the gateway in the format of {cert-manager.io/issuer: "letsencrypt-prod"} + additionalAnnotations: { } diff --git a/helm/pygluu/kubernetes/templates/ldap/base/101-ox.yaml b/helm/pygluu/kubernetes/templates/ldap/base/101-ox.yaml new file mode 100644 index 00000000000..7a304edc12f --- /dev/null +++ b/helm/pygluu/kubernetes/templates/ldap/base/101-ox.yaml @@ -0,0 +1,2833 @@ +apiVersion: v1 +data: + 101-ox.ldif: |+ + dn: cn=schema + objectClass: top + objectClass: ldapSubentry + objectClass: subschema + cn: schema + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.1 NAME ( 'oxAssociatedClient' 'associatedClient' ) + DESC 'Associate the dn of an OAuth2 client with a person or UMA Resource Set.' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.2 NAME 'associatedPerson' + DESC 'Reference the dn of a person.' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.3 NAME 'blowfishPassword' + DESC 'Blowfish crypted text' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.4 NAME 'county' + DESC 'ISO 3166-1 Alpha-2 Country Code' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.5 NAME 'creationDate' + DESC 'Creation Date used for password reset requests' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.6 NAME 'defaultScope' + DESC 'Track the default scope for an custom OAuth2 Scope.' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.7 NAME 'deployedAppliances' + DESC 'Track which appliances are deployed at an organization.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.8 NAME 'federationRules' + DESC 'Track rules for the federation in Gluu SAML config. Deprecated as multi-party federation management should move to Jagger.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.9 NAME 'gluuAddPersonCapability' + DESC 'Organizational attribute to control whether new users can be added via the oxTrust GUI.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.10 NAME 'gluuAdditionalBandwidth' + DESC 'Track bandwidth requirements for the Gluu Server instance' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.11 NAME 'gluuAdditionalMemory' + DESC 'Track additional memory requirements for the Gluu Server instance.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.12 NAME 'gluuAdditionalUsers' + DESC 'TODO : use unclear' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.13 NAME 'gluuApplianceDnsServer' + DESC 'Persist the DNS server that should be used for the Gluu Server instance.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.14 NAME 'gluuAppliancePollingInterval' + DESC 'Set the frequency of the health status update of the Gluu Server' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.15 NAME ( 'gluuApplianceUpdateRequestList' 'gluuApplianceUpdateReuestList' ) + DESC 'Used by the Gluu Server to request an update' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.16 NAME 'gluuAttributeViewType' + DESC 'Specify in oxTrust who can view an attribute, admin or user' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.17 NAME 'gluuAttributeEditType' + DESC 'Specify in oxTrust who can update an attribute, admin or user' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.18 NAME 'gluuAttributeName' + DESC 'Specify an identifier for an attribute. May be multi-value where an attribute has two names, like givenName and first-name.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.19 NAME 'gluuAttributeOrigin' + DESC 'Specify the person objectclass associated with the attribute, used for display purposes in oxTrust.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.20 NAME 'gluuAttributeSystemEditType' + DESC 'TODO - still required?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.21 NAME 'gluuAttributeType' + DESC 'Data type of attribute. Values can be string, photo, numeric, date' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.22 NAME 'gluuAttributeUsageType' + DESC 'TODO - Usage? Value can be OpenID' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.23 NAME 'gluuBandwidthRX' + DESC 'Track data received by the Gluu Server' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.24 NAME 'gluuBandwidthTX' + DESC 'Track data sent by the Gluu Server' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.25 NAME 'gluuCategory' + DESC 'TODO - in use? Used to group attributes together.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.26 NAME 'gluuContainerFederation' + DESC 'SAML Trust Relationship federation info' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.27 NAME 'gluuCustomMessage' + DESC 'oxTrust custom welcome message' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.28 NAME 'gluuDSstatus' + DESC 'Monitor health of the instance LDAP server.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.29 NAME 'gluuEntityId' + DESC 'Specifies SAML trust relationship entity ID' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.30 NAME 'gluuFaviconImage' + DESC 'TODO - Stores URL of favicon' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.31 NAME 'gluuFederationHostingEnabled' + DESC 'oxTrust flag for the federation feature. Values enabled or disabled.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.32 NAME 'gluuFreeDiskSpace' + DESC 'Monitor free disk space on the Gluu Server instance' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.33 NAME 'gluuFreeMemory' + DESC 'Monitor free memory on the Gluu Server instance' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.34 NAME 'gluuFreeSwap' + DESC 'Monitor swap space on the Gluu Server instance' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.35 NAME 'gluuHTTPstatus' + DESC 'Monitor HTTP availability of the Gluu Server instance' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.36 NAME 'gluuGroupCount' + DESC 'Monitor the number of groups. TODO - Remove?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.37 NAME 'gluuGroupType' + DESC 'Type of Group. Not used.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.38 NAME 'gluuGroupVisibility' + DESC 'Group visibility. Not used.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.39 NAME 'gluuHostname' + DESC 'The hostname of the Gluu Server instance' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.40 NAME 'gluuInvoiceAmount' + DESC 'TODO - in use?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.41 NAME 'gluuInvoiceDate' + DESC 'TODO - in use?' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.42 NAME 'gluuInvoiceLineItemName' + DESC 'TODO - in use?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.43 NAME 'gluuInvoiceNo' + DESC 'TODO - in use?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.44 NAME 'gluuInvoiceNumber' + DESC 'TODO - in use?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.45 NAME 'gluuInvoiceProductNumber' + DESC 'TODO - in use?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.46 NAME 'gluuInvoiceQuantity' + DESC 'TODO - in use?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.47 NAME 'gluuInvoiceStatus' + DESC 'TODO - in use?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.48 NAME 'gluuIpAddress' + DESC 'IP address of the Gluu Server instance' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.49 NAME 'gluuIsFederation' + DESC 'Used in oxTrust to specify if a SAML Trust Relationship is a federation. It could also be a website' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.50 NAME 'gluuLastUpdate' + DESC 'Monitors last time the server was able to connect to the monitoring system.' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.51 NAME ( 'gluuLifeRay' 'TODO-remove' ) + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.52 NAME 'gluuLoadAvg' + DESC 'Montior the average CPU load for a Gluu Server instance.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.53 NAME 'gluuLogoImage' + DESC 'Logo used by oxTrust for default look and feel.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.54 NAME 'gluuManageIdentityPermission' + DESC 'TODO - in use?' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.55 NAME 'gluuManagedOrganizations' + DESC 'Used to track with which organizations a person is associated' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.56 NAME 'gluuManager' + DESC 'Used to specify if a person has the manager role' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.57 NAME 'gluuManagerGroup' + DESC 'Used in organizatoin entry to specifies the dn of the group that has admin priviledges in oxTrust.' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.58 NAME 'gluuMaxLogSize' + DESC 'Maximum Log File Size' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.59 NAME 'gluuOptOuts' + DESC 'White pages attributes restricted by person in oxTrust profile management' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.60 NAME 'gluuOrgProfileMgt' + DESC 'enable or disable profile management feature in oxTrust' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.61 NAME 'gluuOrgShortName' + DESC 'Short description, as few letters as possible, no spaces.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.62 NAME 'gluuPaidUntil' + DESC 'TODO - in use?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.63 NAME 'gluuPaymentProcessorTimestamp' + DESC 'TODO - in use?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.64 NAME 'gluuPersonCount' + DESC 'Monitor the number of people in the LDAP severs for a Gluu Server instance' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.65 NAME 'gluuPrivate' + DESC 'TODO - in use?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.66 NAME 'gluuProStoresUser' + DESC 'TODO - remove' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.67 NAME 'gluuProfileConfiguration' + DESC 'SAML Trust Relationship attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.68 NAME 'gluuPublishIdpMetadata' + DESC 'Gluu Server flag to publish the IDP metadata via the web server' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.69 NAME 'gluuReleasedAttribute' + DESC 'oxTrust reference for the dn of the released attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.70 NAME 'gluuResizeInitiated' + DESC 'TODO - in use?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.71 NAME 'gluuRulesAccepted' + DESC 'TODO - use unknown for Gluu SAML config' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.72 NAME 'gluuSAML1URI' + DESC 'SAML 1 uri of attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.73 NAME 'gluuSAML2URI' + DESC 'SAML 2 uri of attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.74 NAME 'gluuSAMLMetaDataFilter' + DESC 'Metadata filter in SAML trust relationship' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.75 NAME 'gluuSAMLTrustEngine' + DESC 'SAML trust relationship configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.76 NAME 'gluuSAMLmaxRefreshDelay' + DESC 'SAML trust relationship refresh time' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.77 NAME 'gluuSAMLspMetaDataFN' + DESC 'SAML Trust Relationship file location of metadata' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.78 NAME 'gluuSAMLspMetaDataSourceType' + DESC 'SAML Trust Relationship SP metadata type - file, URI, federation' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.79 NAME 'gluuSAMLspMetaDataURL' + DESC 'SAML Trust Relationship URI location of metadata' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.80 NAME 'gluuSLAManager' + DESC 'Specifies if the person has the SLA manager role' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.81 NAME 'gluuSPTR' + DESC 'TODO - in use?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.82 NAME 'gluuScimEnabled' + DESC 'oxTrust SCIM feature - enabled or disabled' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.83 NAME 'gluuShibAssertionsIssued' + DESC 'Monitors activity of Gluu Server Shibboleth IDP' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.84 NAME 'gluuShibFailedAuth' + DESC 'Monitors failed login attempts on Gluu Server Shibboleth IDP' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.85 NAME 'gluuShibSecurityEvents' + DESC 'Monitors security events on Gluu Server Shibboleth IDP' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.86 NAME 'gluuShibSuccessfulAuths' + DESC 'Monitors login attempts on Gluu Server Shibboleth IDP' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.87 NAME 'gluuSmtpFromEmailAddress' + DESC 'Gluu Server SMTP configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.88 NAME 'gluuSmtpFromName' + DESC 'SMTP From Name' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.89 NAME 'gluuSmtpHost' + DESC 'SMTP Host' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.90 NAME 'gluuSmtpPassword' + DESC 'SMTP User Password' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.91 NAME 'gluuSmtpPort' + DESC 'SMTP Port' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.92 NAME 'gluuSmtpRequiresAuthentication' + DESC 'SMTP Requires Authentication' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.93 NAME 'gluuSmtpRequiresSsl' + DESC 'SMTP Requires SSL' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.94 NAME 'gluuSmtpUserName' + DESC 'SMTP User Name' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.95 NAME 'gluuSpecificRelyingPartyConfig' + DESC 'SAML Trust Relationship configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.96 NAME 'gluuSslExpiry' + DESC 'SAML Trust Relationship configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.97 NAME 'gluuStatus' + DESC 'Status of the entry, used by many objectclasses' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.98 NAME 'gluuSystemUptime' + DESC 'Monitors how long the Gluu Server instance has been running.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.99 NAME 'gluuTargetRAM' + DESC 'Monitors total available RAM on Gluu Server instance' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.100 NAME 'gluuTempFaviconImage' + DESC 'Store location for upload of Favicon' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.101 NAME 'gluuThemeColor' + DESC 'oxTrust login page configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.102 NAME 'gluuTrustContact' + DESC 'oxTrust login page configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.103 NAME 'gluuTrustDeconstruction' + DESC 'TODO - in use?' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.104 NAME 'gluuUrl' + DESC 'Gluu instance URL' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.105 NAME 'gluuVDSenabled' + DESC 'oxTrust VDS enabled or disabled' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.106 NAME 'gluuVDSstatus' + DESC 'Gluu VDS configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.107 NAME 'gluuValidationLog' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.108 NAME 'gluuValidationStatus' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.109 NAME 'gluuVdsCacheRefreshEnabled' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.110 NAME 'gluuVdsCacheRefreshLastUpdate' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.111 NAME 'gluuVdsCacheRefreshLastUpdateCount' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.112 NAME 'gluuVdsCacheRefreshPollingInterval' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.113 NAME 'gluuVdsCacheRefreshProblemCount' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.114 NAME 'gluuWhitePagesEnabled' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.115 NAME 'gluuWhitePagesListed' + DESC 'Allow Publication' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.116 NAME 'iname' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.117 NAME 'inum' + DESC 'XRI i-number' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.118 NAME 'inumFN' + DESC 'XRI i-number sans punctuation' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.119 NAME 'literalBinaryValue' + DESC 'OX literalValue' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.120 NAME 'literalValue' + DESC 'OX literalValue' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.121 NAME 'memberOf' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.122 NAME 'nonProfit' + DESC 'TODO - in use?' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.123 NAME 'organizationalOwner' + DESC 'OX organizationalOwner' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.124 NAME 'oxAmHost' + DESC 'am host' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.125 NAME 'oxAuthClaimName' + DESC 'Used by oxAuth in conjunction with gluuttributeName to map claims to attributes in LDAP.' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.126 NAME 'oxAuthAppType' + DESC 'oxAuth App Type' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.127 NAME 'authnTime' + DESC 'oxAuth Authentication Time' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.128 NAME 'authzCode' + DESC 'oxAuth authorization code' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.129 NAME 'oxAuthClaim' + DESC 'oxAuth Attribute Claim' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.130 NAME 'oxAuthGroupClaims' + DESC 'oxAuth Group Attribute Claims (true or false)' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.131 NAME 'oxAuthClientId' + DESC 'oxAuth Client id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.132 NAME 'clnId' + DESC 'oxAuth Client id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.133 NAME 'oxAuthClientIdIssuedAt' + DESC 'oxAuth Client Issued At' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.134 NAME 'oxAuthClientSecret' + DESC 'oxAuth Client Secret' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.135 NAME 'oxAuthClientSecretExpiresAt' + DESC 'Date client expires' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.136 NAME 'oxAuthClientURI' + DESC 'oxAuth Client URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.137 NAME 'oxAuthConfDynamic' + DESC 'oxAuth Dynamic Configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.138 NAME 'oxAuthConfErrors' + DESC 'oxAuth Errors Configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.139 NAME 'oxAuthConfStatic' + DESC 'oxAuth Static Configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.140 NAME 'oxAuthConfWebKeys' + DESC 'oxAuth Web Keys Configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.141 NAME 'oxAuthContact' + DESC 'oxAuth Contact' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.142 NAME 'iat' + DESC 'oxAuth Creation' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.143 NAME 'oxAuthDefaultAcrValues' + DESC 'oxAuth Default Acr Values' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.144 NAME 'oxAuthDefaultMaxAge' + DESC 'oxAuth Default Max Age' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.145 NAME 'exp' + DESC 'oxAuth Expiration' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.146 NAME 'grtId' + DESC 'oxAuth grant id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.147 NAME 'oxAuthGrantType' + DESC 'oxAuth Grant Type' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.148 NAME 'grtTyp' + DESC 'oxAuth Grant Type' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.149 NAME 'oxAuthIdTokenEncryptedResponseAlg' + DESC 'oxAuth ID Token Encrypted Response Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.150 NAME 'oxAuthIdTokenEncryptedResponseEnc' + DESC 'oxAuth ID Token Encrypted Response Enc' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.151 NAME 'oxAuthIdTokenSignedResponseAlg' + DESC 'oxAuth ID Token Signed Response Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.152 NAME 'oxAuthInitiateLoginURI' + DESC 'oxAuth Initiate Login URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.153 NAME 'oxAuthJwksURI' + DESC 'oxAuth JWKs URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.154 NAME 'oxAuthJwks' + DESC 'oxAuth JWKs' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.155 NAME 'jwtReq' + DESC 'oxAuth JWT Request' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.156 NAME 'oxAuthLogoURI' + DESC 'oxAuth Logo URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.157 NAME 'nnc' + DESC 'oxAuth nonce' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.158 NAME 'oxSessionState' + DESC 'oxAuth Session State' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.159 NAME 'oxAuthPermissionGrantedMap' + DESC 'oxAuth Permission Granted Map' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.160 NAME 'oxAuthPersistentJWT' + DESC 'oxAuth Persistent JWT' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.161 NAME 'oxAuthPolicyURI' + DESC 'oxAuth Policy URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.162 NAME 'oxAuthLogoutURI' + DESC 'oxAuth Policy URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.163 NAME 'oxAuthLogoutSessionRequired' + DESC 'oxAuth Policy URI' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.164 NAME 'oxAuthPostLogoutRedirectURI' + DESC 'oxAuth Post Logout Redirect URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.165 NAME 'oxAuthRedirectURI' + DESC 'oxAuth Redirect URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.166 NAME 'oxAuthRegistrationAccessToken' + DESC 'oxAuth Registration Access Token' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.167 NAME 'oxAuthReleasedScope' + DESC 'oxAuth released scope attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.168 NAME 'oxAuthRequestObjectSigningAlg' + DESC 'oxAuth Request Object Signing Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.169 NAME 'oxAuthRequestObjectEncryptionAlg' + DESC 'oxAuth Request Object Encryption Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.170 NAME 'oxAuthRequestObjectEncryptionEnc' + DESC 'oxAuth Request Object Encryption Enc' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.171 NAME 'oxAuthRequestURI' + DESC 'oxAuth Request URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.172 NAME 'oxAuthRequireAuthTime' + DESC 'oxAuth Require Authentication Time' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.173 NAME 'oxAuthResponseType' + DESC 'oxAuth Response Type' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.174 NAME 'oxAuthScope' + DESC 'oxAuth Attribute Scope' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.175 NAME 'scp' + DESC 'oxAuth Attribute Scope' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.176 NAME 'oxScopeType' + DESC 'OX Attribute Scope type' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.177 NAME 'oxAuthSectorIdentifierURI' + DESC 'oxAuth Sector Identifier URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.178 NAME 'oxAuthSignedResponseAlg' + DESC 'oxAuth Signed Response Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.179 NAME 'oxAuthSkipAuthorization' + DESC 'oxAuth skip authorization attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.180 NAME 'oxAuthSubjectType' + DESC 'oxAuth Subject Type' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.181 NAME 'tknCde' + DESC 'oxAuth Token Code' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.182 NAME 'oxAuthTokenEndpointAuthMethod' + DESC 'oxAuth Token Endpoint Auth Method' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.183 NAME 'oxAuthTokenEndpointAuthSigningAlg' + DESC 'oxAuth Token Endpoint Auth Signing Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.184 NAME 'tknTyp' + DESC 'oxAuth Token Type' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.185 NAME 'oxAuthTosURI' + DESC 'oxAuth TOS URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.186 NAME 'oxAuthTrustedClient' + DESC 'oxAuth Trusted Client' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.187 NAME 'oxAuthUmaScope' + DESC 'URI reference of scope descriptor' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.188 NAME 'oxAuthUserDN' + DESC 'oxAuth User DN' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.189 NAME ( 'oxAuthUserId' 'usrId' ) + DESC 'oxAuth user id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.190 NAME 'oxAuthUserInfoEncryptedResponseAlg' + DESC 'oxAuth User Info Encrypted Response Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.191 NAME 'oxAuthUserInfoEncryptedResponseEnc' + DESC 'oxAuth User Info Encrypted Response Enc' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.192 NAME 'oxAuthExtraConf' + DESC 'oxAuth additional configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.193 NAME 'oxAuthX509PEM' + DESC 'oxAuth x509 in PEM format' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.194 NAME 'oxAuthX509URL' + DESC 'oxAuth x509 URL' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.195 NAME 'oxAuthenticationMode' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.196 NAME 'acr' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.197 NAME 'oxTrustAuthenticationMode' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.198 NAME 'oxConfigurationCode' + DESC 'ox configuration code' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.199 NAME 'oxCreationTimestamp' + DESC 'Registration time' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.200 NAME 'oxDomain' + DESC 'domain' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.201 NAME 'oxExternalUid' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.202 NAME 'oxOTPCache' + DESC 'Stores a used OTP to prevent a hacker from using it again. Complementary to oxExternalUid attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.203 NAME 'oxFaviconImage' + DESC 'URI for a graphic icon' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.204 NAME 'oxGroup' + DESC 'User group' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.205 NAME 'oxGuid' + DESC 'A random string to mark temporary tokens' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.206 NAME 'uuid' + DESC 'Unique identifier' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.207 NAME 'oxHost' + DESC 'ox host' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.208 NAME 'oxIDPAuthentication' + DESC 'Custom IDP authentication configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.209 NAME 'oxIconUrl' + DESC 'ox icon url' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.210 NAME 'oxId' + DESC 'Identifier' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.211 NAME 'sid' + DESC 'Session Identifier' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.212 NAME 'oxAsJwt' + DESC 'Boolean field to indicate whether object is used as JWT' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.213 NAME 'oxJwt' + DESC 'JWT representation of the object or otherwise jwt associated with the object' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.214 NAME 'oxInvolvedClients' + DESC 'Involved clients' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.215 NAME 'oxLastAccessTime' + DESC 'Last access time' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.216 NAME 'oxLastLogonTime' + DESC 'Last logon time' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.217 NAME 'oxLinkCreator' + DESC 'Link Creator' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.218 NAME 'oxLinkExpirationDate' + DESC 'Link Expiration Date' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.219 NAME 'oxLinkLinktrack' + DESC 'Linktrack link' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.220 NAME 'oxLinkModerated' + DESC 'Is Link Moderated?' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.221 NAME 'oxLinkModerators' + DESC 'Link Moderators' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.222 NAME 'oxLinkPending' + DESC 'Pending Registrations' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.223 NAME 'oxLinktrackEnabled' + DESC 'Is Linktrack API configured' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.224 NAME 'oxLinktrackLogin' + DESC 'Linktrack API login' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.225 NAME 'oxLinktrackPassword' + DESC 'Linktrack API password' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.226 NAME 'oxLogViewerConfig' + DESC 'Log viewer configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.227 NAME 'oxMultivaluedAttribute' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.228 NAME 'oxName' + DESC 'Name' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.229 NAME 'oxNameIdType' + DESC 'NameId Type' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.230 NAME 'oxPolicyRule' + DESC 'Policy Rule' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.231 NAME 'oxUmaPolicyScriptDn' + DESC 'OX policy script Dn' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.232 NAME 'oxProxConf' + DESC 'oxProx Configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.233 NAME 'oxProxyAccessToken' + DESC 'oxProx access token' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.234 NAME 'oxProxyClaimMapping' + DESC 'oxProx claim mapping' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.235 NAME 'oxState' + DESC 'oxState' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.236 NAME 'oxCounter' + DESC 'oxCounter' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.237 NAME 'oxStatus' + DESC 'oxStatus' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.238 NAME 'oxApplication' + DESC 'oxApplication' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.239 NAME 'oxDeviceRegistrationConf' + DESC 'oxDeviceRegistrationConf' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.240 NAME 'oxDeviceKeyHandle' + DESC 'oxDeviceKeyHandle' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.241 NAME 'oxDeviceHashCode' + DESC 'oxDeviceHashCode' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.242 NAME 'oxRequest' + DESC 'oxRequest' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.243 NAME 'oxRequestId' + DESC 'oxRequestId' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.244 NAME 'oxDeviceData' + DESC 'oxDeviceData' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.245 NAME 'oxEnrollmentCode' + DESC 'oxEnrollmentCode' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.246 NAME 'oxProxyClientId' + DESC 'oxProx client id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.247 NAME 'oxProxyScope' + DESC 'oxProx scope' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.248 NAME 'oxProxyToOpClientMapping' + DESC 'oxProx client mapping to op client' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.249 NAME 'oxPushApplication' + DESC 'oxPush application DN' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.250 NAME 'oxPushApplicationConf' + DESC 'oxPush application configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.251 NAME 'oxPushDeviceConf' + DESC 'oxPush device configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.252 NAME 'oxRegistrationConfiguration' + DESC 'Registration Configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.253 NAME 'oxResource' + DESC 'Host path' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.254 NAME 'oxResourceSetId' + DESC 'ox resource set id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.255 NAME 'oxRevision' + DESC 'Revision' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.256 NAME 'oxLevel' + DESC 'Level' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.257 NAME 'oxSCIMCustomAttribute' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.258 NAME 'oxScript' + DESC 'Attribute that contains script (python, java script)' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.259 NAME 'oxScriptDn' + DESC 'Script object DN' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.260 NAME 'oxScriptType' + DESC 'Attribute that contains script type (e.g. python, java script)' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.261 NAME 'oxScriptError' + DESC 'Attribute that contains first error which application get during it execution' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.262 NAME 'oxSmtpConfiguration' + DESC 'SMTP configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.263 NAME 'oxSourceAttribute' + DESC 'Source Attribute for this Attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.264 NAME 'oxTicket' + DESC 'ox ticket' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.265 NAME 'oxTrustActive' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.266 NAME 'oxTrustCacheRefreshServerIpAddress' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Specifies the oxTrust server which should run Cache refresh' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.267 NAME 'oxTrustAddresses' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.268 NAME 'oxTrustConfApplication' + DESC 'oxTrust Application Configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.269 NAME 'oxTrustConfCacheRefresh' + DESC 'oxTrust Cache Refresh Configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.270 NAME 'oxConfApplication' + DESC 'ox Application Configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.271 NAME 'oxTrustCustAttrB' + DESC 'scim status' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.272 NAME 'oxTrustEmail' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.273 NAME 'oxTrustEntitlements' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.274 NAME 'oxTrustExternalId' + EQUALITY caseExactMatch + SUBSTR caseExactSubStringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.275 NAME 'oxTrustImsValue' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.276 NAME 'oxTrustMetaCreated' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.277 NAME 'oxTrustMetaLastModified' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.278 NAME 'oxTrustMetaLocation' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.279 NAME 'oxTrustMetaVersion' + EQUALITY caseExactMatch + SUBSTR caseExactSubStringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.280 NAME 'oxTrustNameFormatted' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.281 NAME 'oxTrustPhoneValue' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.282 NAME 'oxTrustPhotos' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.283 NAME 'oxTrustProfileURL' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.284 NAME 'oxTrustRole' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.285 NAME 'oxTrustStoreCert' + DESC 'oxPush device configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.286 NAME 'oxTrustStoreConf' + DESC 'oxPush application configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.287 NAME 'oxTrustTitle' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.288 NAME 'oxTrustUserType' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.289 NAME 'oxTrusthonorificPrefix' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.290 NAME 'oxTrusthonorificSuffix' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.291 NAME 'oxTrustx509Certificate' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.292 NAME 'oxType' + DESC 'ox type' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.293 NAME 'oxUmaPermission' + DESC 'ox uma permission' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.294 NAME 'oxUrl' + DESC 'ox url' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.295 NAME 'oxX509PEM' + DESC 'x509 in PEM format' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.296 NAME 'oxX509URL' + DESC 'x509 URL' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.297 NAME 'passwordResetAllowed' + DESC 'Is password reset mechanics allowed' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.298 NAME 'persistentId' + DESC 'PersistentId' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Persistent ID reserved for SAML' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.299 NAME 'personInum' + DESC 'Inum of a person' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.300 NAME 'primaryKeyAttrName' + DESC 'Primary Key Attribute Name' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.301 NAME 'primaryKeyValue' + DESC 'Primary Key Value' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.302 NAME 'proStoresToken' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.303 NAME 'programmingLanguage' + DESC 'programming language' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.304 NAME 'prostoresTimestamp' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.305 NAME 'registrationDate' + DESC 'Registration date' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.306 NAME 'role' + DESC 'Role' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.307 NAME 'scimAuthMode' + DESC 'SCIM Authorization mode' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.308 NAME 'scimGroup' + DESC 'scim Group' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.309 NAME 'scimStatus' + DESC 'scim status' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.310 NAME 'secondaryKeyAttrName' + DESC 'Secondary Key Attribute Name' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.311 NAME 'secondaryKeyValue' + DESC 'Secondary Key Value' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.312 NAME 'secretAnswer' + DESC 'Secret Answer' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.313 NAME 'secretQuestion' + DESC 'Secret Question' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.314 NAME 'softwareVersion' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.315 NAME 'sourceRelationalXdiStatement' + DESC 'OX SourceRelationalXdiStatement' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.316 NAME 'targetRelationalXdiStatement' + DESC 'OX TargetRelationalXdiStatement' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.317 NAME 'tertiaryKeyAttrName' + DESC 'Tertiary Key Attribute Name' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.318 NAME 'tertiaryKeyValue' + DESC 'Tertiary Key Value' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.319 NAME 'transientId' + DESC 'TransientId' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.320 NAME 'url' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.321 NAME 'urn' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.322 NAME 'x' + DESC 'OX XRI Component' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.323 NAME 'xdiStatement' + DESC 'OX xdiStatement' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.324 NAME 'xri' + DESC 'OX XRI address' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.325 NAME ( 'middleName' 'oxTrustMiddleName' ) + DESC 'Middle name(s)' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.326 NAME ( 'nickname' 'oxTrustnickname' ) + DESC 'Casual name of the End-User' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.327 NAME 'preferredUsername' + DESC 'Shorthand Name' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.328 NAME 'profile' + DESC 'Profile page URL of the person' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.329 NAME ( 'picture' 'photo1' ) + DESC 'Profile picture URL of the person' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.330 NAME 'website' + DESC 'Web page or blog URL of the person' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.331 NAME 'emailVerified' + DESC 'True if the e-mail address of the person has been verified; otherwise false' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.332 NAME 'gender' + DESC 'Gender of the person either female or male' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.333 NAME 'birthdate' + DESC 'Birthday of the person, represented as an ISO 8601:2004 [ISO8601‑2004] YYYY-MM-DD format' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.334 NAME ( 'zoneinfo' 'timezone' ) + DESC 'Time zone database representing the End-Users time zone. For example, Europe/Paris or America/Los_Angeles' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.335 NAME ( 'locale' 'oxTrustLocale' ) + DESC 'Locale of the person, represented as a BCP47 [RFC5646] language tag' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.336 NAME 'phoneNumberVerified' + DESC 'True if the phone number of the person has been verified, otherwise false' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.337 NAME 'address' + DESC 'OpenID Connect formatted JSON object representing the address of the person' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.338 NAME 'updatedAt' + DESC 'Time the information of the person was last updated. Seconds from 1970-01-01T0:0:0Z' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.339 NAME 'gluuRegExp' + DESC 'Regular expression used to validate attribute data' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.340 NAME 'gluuTooltip' + DESC 'Custom tooltip to be shown on the UI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'OpenID Connect 1.0 Standard Claim' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.341 NAME 'oxModuleProperty' + DESC 'Module property' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.342 NAME 'oxConfigurationProperty' + DESC 'Configuration property' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.343 NAME 'oxAuthSessionAttribute' + DESC 'oxAuthSessionAttribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.344 NAME 'researchAndScholarshipEnabled' + DESC 'Trust relationship attribute to show that InCommon R&S activated' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.345 NAME 'oxStartDate' + DESC 'Start date' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.346 NAME 'oxEndDate' + DESC 'End date' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.347 NAME 'oxApplicationType' + DESC 'Application type' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.348 NAME 'oxMetricType' + DESC 'Metric type' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.349 NAME 'oxData' + DESC 'OX data' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.350 NAME 'dat' + DESC 'OX data' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.351 NAME 'oxCodeChallenge' + DESC 'OX PKCE code challenge' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.352 NAME 'chlng' + DESC 'OX PKCE code challenge' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.353 NAME 'chlngMth' + DESC 'OX PKCE code challenge method' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.354 NAME 'oxSectorIdentifier' + DESC 'ox Sector Identifier' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.355 NAME 'oxPersistClientAuthorizations' + DESC 'ox Persist Client Authorizations' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.356 NAME 'oxTrustConfImportPerson' + DESC 'oxTrust Import Person Configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.357 NAME 'oxSessionStateId' + DESC 'oxSessionStateId' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.358 NAME 'ssnId' + DESC 'oxAuth Session DN' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.359 NAME 'oxPasswordExpirationDate' + DESC 'Password Expiration date, represented as an ISO 8601 (YYYY-MM-DD) format' + EQUALITY generalizedTimeMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ORDERING generalizedTimeOrderingMatch + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.360 NAME 'oxCountInvalidLogin' + DESC 'Invalid login attempts count' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.361 NAME 'gluuIMAPData' + DESC 'This data has information about your imap connection' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.362 NAME 'gluuPassportConfiguration' + DESC 'oxTrust Passport Strategy Configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.363 NAME 'gluuPassportEnabled' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.364 NAME 'gluuRadiusEnabled' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.365 NAME 'gluuSamlEnabled' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.366 NAME 'oxValidation' + DESC 'This data has information about attribute Validation' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.367 NAME 'gluuEntityType' + DESC 'This data has information about TR EntityType' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.368 NAME 'oxPPID' + DESC 'Persistent Pairwise ID for OpenID Connect' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.369 NAME 'oxAuthSessionId' + DESC 'oxAuth Session Id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.370 NAME 'oxCacheConfiguration' + DESC 'Cache configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.371 NAME 'oxLogConfigLocation' + DESC 'Path to external log4j2.xml' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.372 NAME 'oxIncludeClaimsInIdToken' + DESC 'ox Include Claims In Id Token' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.373 NAME 'oxClaimValues' + DESC 'Claim Values' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.374 NAME 'oxClaimRedirectURI' + DESC 'Claim Redirect URI' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.375 NAME 'oxAttributes' + DESC 'Attributes' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.376 NAME 'attr' + DESC 'Attributes' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.377 NAME 'userRandomKey' + DESC 'Attributes' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.378 NAME 'oxRefreshTokenLifetime' + DESC 'Lifetime of refresh token' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.379 NAME 'oxTrustConfAttributeResolver' + DESC 'oxTrust Attribute Resolver ' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.380 NAME 'oxAuthPermissionGranted' + DESC 'oxAuth Permission Granted' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.381 NAME 'oxNickName' + DESC 'oxNickName' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.382 NAME 'oxDeviceNotificationConf' + DESC 'Extended push notification configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.383 NAME 'clms' + DESC 'oxAuth Claims' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.384 NAME 'oxDisabled' + DESC 'Status of client' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.385 NAME 'oxWebKeysSettings' + DESC 'oxAuth Web Keys Configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.386 NAME 'oxScopeExpression' + DESC 'Scope expression' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.387 NAME 'oxPreferredMethod' + DESC 'Gluu Casa - preferred method to use for user authentication' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.388 NAME 'oxOTPDevices' + DESC 'Gluu Casa - Json representation of OTP devices. Complementary to oxExternalUid attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.389 NAME 'oxMobileDevices' + DESC 'Gluu Casa - Json representation of mobile devices. Complementary to mobile attribute' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.390 NAME 'oxdId' + DESC 'oxd Id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.391 NAME 'oxAuthAuthorizedOrigins' + DESC 'oxAuth Authorized Origins' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.392 NAME 'oxStrongAuthPolicy' + DESC 'Gluu Casa - 2FA Enforcement Policy for User' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.393 NAME 'oxTrustedDevicesInfo' + DESC 'Gluu Casa - List of devices with which strong authentication may be skipped' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.394 NAME 'tknBndCnf' + DESC 'oxauth - Token Binding Id Hash' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.395 NAME 'oxUnlinkedExternalUids' + DESC 'Gluu Casa - List of unlinked social accounts (ie disabled oxExternalUids)' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.396 NAME 'oxAccessTokenAsJwt' + DESC 'oxauth - indicator whether to return access token as JWT' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.397 NAME 'oxAccessTokenSigningAlg' + DESC 'oxauth - access token signing algorithm' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.398 NAME 'oxRegistrationData' + DESC 'oxRegistrationData' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.399 NAME 'oxAuthenticationData' + DESC 'oxAuthenticationData' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.400 NAME 'oxPublicKeyId' + DESC 'oxPublicKeyId' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.401 NAME 'oxAccessTokenLifetime' + DESC 'Lifetime of access token' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.402 NAME 'oxSoftwareId' + DESC 'Software Identifier' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.403 NAME 'oxSoftwareVersion' + DESC 'Software Version' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.404 NAME 'oxSoftwareStatement' + DESC 'Software Statement' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.405 NAME 'oxRptAsJwt' + DESC 'oxRptAsJwt' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.406 NAME 'oxCodeChallengeHash' + DESC 'OX code challenge hash' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.407 NAME 'del' + DESC 'del' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.408 NAME 'oxEnabled' + DESC 'Status of the entry, used by many objectclasses' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.409 NAME 'oxAlias' + DESC 'oxAlias' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.410 NAME 'oxTrustLogoPath' + DESC 'oxTrustLogoPath' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.411 NAME 'oxTrustFaviconPath' + DESC 'oxTrustFaviconPath' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.412 NAME 'oxAuthLogoPath' + DESC 'oxAuthLogoPath' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.413 NAME 'oxAuthFaviconPath' + DESC 'oxAuthFaviconPath' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.414 NAME 'idpLogoPath' + DESC 'idpLogoPath' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.415 NAME 'idpFaviconPath' + DESC 'idpFaviconPath' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.416 NAME 'parent' + DESC 'parent' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.417 NAME 'classRef' + DESC 'classRef' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.418 NAME 'gluuSmtpServerTrust' + DESC 'Trust SMTP server' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.419 NAME 'pattern' + DESC 'pattern' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.420 NAME 'oxAuthBackchannelTokenDeliveryMode' + DESC 'oxAuth Backchannel Token Delivery Mode' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.421 NAME 'oxAuthBackchannelClientNotificationEndpoint' + DESC 'oxAuth Backchannel Client Notification Endpoint' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.422 NAME 'oxAuthBackchannelAuthenticationRequestSigningAlg' + DESC 'oxAuth Backchannel Authentication Request Signing Alg' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.423 NAME 'oxAuthBackchannelUserCodeParameter' + DESC 'oxAuth Backchannel User Code Parameter' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.424 NAME 'oxAuthBackchannelDeviceRegistrationToken' + DESC 'oxAuth Backchannel Device Registration Token' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.425 NAME 'oxAuthBackchannelUserCode' + DESC 'oxAuth Backchannel User Code' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.426 NAME 'oxDocumentStoreConfiguration' + DESC 'oxDocumentStoreConfiguration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.427 NAME 'gluuConfDynamic' + DESC 'Gluu Dynamic Configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.428 NAME 'gluuConfStatic' + DESC 'Gluu Static Configuration' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.429 NAME 'authReqId' + DESC 'Authentication request id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.430 NAME 'gluuConfigurationPollingInterval' + DESC 'gluuConfigurationPollingInterval' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.431 NAME 'gluuConfigurationDnsServer' + DESC 'gluuConfigurationDnsServer' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + attributeTypes: ( 1.3.6.1.4.1.48710.1.3.432 NAME 'jansId' + DESC 'jans id' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'Gluu created attribute' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.1 NAME 'pairwiseIdentifier' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( oxId $ oxSectorIdentifier $ oxAuthClientId $ oxAuthUserId ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.2 NAME 'gluuPerson' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( associatedClient $ c $ displayName $ givenName $ gluuManagedOrganizations $ gluuOptOuts $ gluuStatus $ gluuWhitePagesListed $ iname $ inum $ mail $ gluuSLAManager $ memberOf $ o $ oxAuthPersistentJWT $ oxCreationTimestamp $ oxExternalUid $ oxOTPCache $ oxLastLogonTime $ oxTrustActive $ oxTrustAddresses $ oxTrustEmail $ oxTrustEntitlements $ oxTrustExternalId $ oxTrustImsValue $ oxTrustMetaCreated $ oxTrustMetaLastModified $ oxTrustMetaLocation $ oxTrustMetaVersion $ oxTrustNameFormatted $ oxTrustPhoneValue $ oxTrustPhotos $ oxTrustProfileURL $ oxTrustRole $ oxTrustTitle $ oxTrustUserType $ oxTrusthonorificPrefix $ oxTrusthonorificSuffix $ oxTrustx509Certificate $ oxPasswordExpirationDate $ persistentId $ middleName $ nickname $ preferredUsername $ profile $ picture $ website $ emailVerified $ gender $ birthdate $ zoneinfo $ locale $ phoneNumberVerified $ address $ updatedAt $ preferredLanguage $ role $ secretAnswer $ secretQuestion $ seeAlso $ sn $ cn $ transientId $ uid $ userPassword $ st $ street $ l $ oxCountInvalidLogin $ oxEnrollmentCode $ gluuIMAPData $ oxPPID $ oxGuid $ userRandomKey $ oxPreferredMethod $ userCertificate $ oxOTPDevices $ oxMobileDevices $ oxStrongAuthPolicy $ oxTrustedDevicesInfo $ oxUnlinkedExternalUids $ oxAuthBackchannelDeviceRegistrationToken $ oxAuthBackchannelUserCode ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.3 NAME 'gluuGroup' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( c $ description $ displayName $ gluuGroupType $ gluuGroupVisibility $ gluuStatus $ iname $ inum $ member $ o $ owner $ seeAlso $ oxTrustMetaCreated $ oxTrustMetaLastModified $ oxTrustMetaLocation $ oxTrustMetaVersion ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.4 NAME 'gluuOrganization' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( c $ county $ deployedAppliances $ description $ displayName $ gluuAddPersonCapability $ gluuAdditionalUsers $ gluuApplianceUpdateRequestList $ gluuCustomMessage $ gluuFaviconImage $ gluuFederationHostingEnabled $ gluuInvoiceNo $ gluuLogoImage $ gluuManageIdentityPermission $ gluuManager $ gluuManagerGroup $ gluuOrgShortName $ gluuPaidUntil $ gluuPaymentProcessorTimestamp $ gluuProStoresUser $ gluuStatus $ gluuTempFaviconImage $ gluuThemeColor $ gluuWhitePagesEnabled $ iname $ inum $ l $ mail $ memberOf $ nonProfit $ o $ oxCreationTimestamp $ oxLinkLinktrack $ oxLinktrackEnabled $ oxLinktrackLogin $ oxLinktrackPassword $ oxRegistrationConfiguration $ postalCode $ proStoresToken $ prostoresTimestamp $ scimAuthMode $ scimGroup $ scimStatus $ st $ street $ telephoneNumber $ title $ uid $ userPassword $ oxTrustLogoPath $ oxTrustFaviconPath $ oxAuthLogoPath $ oxAuthFaviconPath $ idpLogoPath $ idpFaviconPath ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.5 NAME 'gluuConfiguration' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( blowfishPassword $ c $ ou $ description $ displayName $ gluuAdditionalBandwidth $ gluuAdditionalMemory $ gluuApplianceDnsServer $ gluuAppliancePollingInterval $ gluuBandwidthRX $ gluuBandwidthTX $ gluuDSstatus $ gluuFederationHostingEnabled $ gluuHTTPstatus $ gluuHostname $ gluuInvoiceNo $ gluuLastUpdate $ gluuLifeRay $ gluuManageIdentityPermission $ gluuManager $ gluuMaxLogSize $ gluuOrgProfileMgt $ gluuPaidUntil $ gluuPaymentProcessorTimestamp $ gluuPrivate $ gluuPublishIdpMetadata $ gluuResizeInitiated $ gluuSPTR $ gluuScimEnabled $ gluuShibAssertionsIssued $ gluuShibFailedAuth $ gluuShibSecurityEvents $ gluuShibSuccessfulAuths $ oxTrustEmail $ gluuSmtpFromEmailAddress $ gluuSmtpFromName $ gluuSmtpHost $ gluuSmtpPassword $ gluuSmtpPort $ gluuSmtpRequiresAuthentication $ gluuSmtpRequiresSsl $ gluuSmtpUserName $ gluuSslExpiry $ gluuStatus $ gluuTargetRAM $ gluuUrl $ gluuVDSenabled $ gluuVDSstatus $ gluuVdsCacheRefreshEnabled $ gluuVdsCacheRefreshLastUpdate $ gluuVdsCacheRefreshLastUpdateCount $ gluuVdsCacheRefreshPollingInterval $ gluuVdsCacheRefreshProblemCount $ gluuWhitePagesEnabled $ iname $ inum $ inumFN $ o $ oxAuthenticationMode $ oxTrustAuthenticationMode $ oxIDPAuthentication $ oxLogViewerConfig $ oxLogConfigLocation $ oxSmtpConfiguration $ oxCacheConfiguration $ oxDocumentStoreConfiguration $ oxTrustStoreCert $ oxTrustStoreConf $ passwordResetAllowed $ softwareVersion $ userPassword $ oxTrustCacheRefreshServerIpAddress $ gluuPassportEnabled $ gluuRadiusEnabled $ gluuSamlEnabled $ gluuSmtpServerTrust $ gluuConfigurationPollingInterval $ gluuConfigurationDnsServer ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.6 NAME 'gluuAttribute' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( description $ displayName $ gluuAttributeEditType $ gluuAttributeName $ gluuAttributeOrigin $ gluuAttributeSystemEditType $ gluuAttributeType $ oxAuthClaimName $ gluuAttributeUsageType $ gluuAttributeViewType $ gluuCategory $ gluuSAML1URI $ gluuSAML2URI $ gluuStatus $ iname $ inum $ oxMultivaluedAttribute $ oxNameIdType $ oxSCIMCustomAttribute $ oxSourceAttribute $ seeAlso $ urn $ gluuRegExp $ gluuTooltip $ oxValidation ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.7 NAME 'gluuSAMLconfig' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( description $ displayName $ federationRules $ gluuContainerFederation $ gluuEntityId $ gluuIsFederation $ gluuProfileConfiguration $ gluuReleasedAttribute $ gluuRulesAccepted $ gluuSAMLMetaDataFilter $ gluuSAMLTrustEngine $ gluuSAMLmaxRefreshDelay $ gluuSAMLspMetaDataFN $ gluuSAMLspMetaDataSourceType $ gluuSAMLspMetaDataURL $ gluuSpecificRelyingPartyConfig $ gluuStatus $ gluuTrustContact $ gluuTrustDeconstruction $ gluuValidationLog $ gluuValidationStatus $ iname $ inum $ o $ oxAuthPostLogoutRedirectURI $ url $ researchAndScholarshipEnabled $ gluuEntityType ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.8 NAME 'gluuInumMap' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( gluuStatus $ inum $ primaryKeyAttrName $ primaryKeyValue $ secondaryKeyAttrName $ secondaryKeyValue $ tertiaryKeyAttrName $ tertiaryKeyValue ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.9 NAME 'gluuInvoice' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( gluuInvoiceAmount $ gluuInvoiceDate $ gluuInvoiceLineItemName $ gluuInvoiceNumber $ gluuInvoiceProductNumber $ gluuInvoiceQuantity $ gluuInvoiceStatus $ inum ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.10 NAME 'gluuPasswordResetRequest' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( creationDate $ oxGuid $ personInum $ del $ exp ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.11 NAME 'oxLink' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( description $ oxGuid $ oxLinkCreator $ oxLinkExpirationDate $ oxLinkLinktrack $ oxLinkModerated $ oxLinkModerators $ oxLinkPending ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.12 NAME 'vdapcontainer' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( ou ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.13 NAME 'vdDirectoryView' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( o ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.14 NAME 'vdlabel' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( o ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.15 NAME 'oxEntry' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( displayName $ iname $ inum ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.16 NAME 'oxNode' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( organizationalOwner $ owner $ sourceRelationalXdiStatement $ targetRelationalXdiStatement $ x $ xdiStatement $ xri ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.17 NAME 'oxAuthClient' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( o $ associatedPerson $ displayName $ description $ inum $ oxAuthAppType $ oxAuthClientIdIssuedAt $ oxAuthClientSecret $ oxAuthClientSecretExpiresAt $ exp $ del $ oxAuthClientURI $ oxAuthContact $ oxAuthDefaultAcrValues $ oxAuthDefaultMaxAge $ oxAuthGrantType $ oxAuthIdTokenEncryptedResponseAlg $ oxAuthIdTokenEncryptedResponseEnc $ oxAuthIdTokenSignedResponseAlg $ oxAuthInitiateLoginURI $ oxAuthJwksURI $ oxAuthJwks $ oxAuthLogoURI $ oxAuthPolicyURI $ oxAuthPostLogoutRedirectURI $ oxAuthRedirectURI $ oxAuthRegistrationAccessToken $ oxAuthRequestObjectSigningAlg $ oxAuthRequestObjectEncryptionAlg $ oxAuthRequestObjectEncryptionEnc $ oxAuthRequestURI $ oxAuthRequireAuthTime $ oxAuthResponseType $ oxAuthScope $ oxAuthClaim $ oxAuthSectorIdentifierURI $ oxAuthSignedResponseAlg $ oxAuthSubjectType $ oxAuthTokenEndpointAuthMethod $ oxAuthTokenEndpointAuthSigningAlg $ oxAuthTosURI $ oxAuthTrustedClient $ oxAuthUserInfoEncryptedResponseAlg $ oxAuthUserInfoEncryptedResponseEnc $ oxAuthExtraConf $ oxClaimRedirectURI $ oxLastAccessTime $ oxLastLogonTime $ oxPersistClientAuthorizations $ oxIncludeClaimsInIdToken $ oxRefreshTokenLifetime $ oxDisabled $ oxAuthLogoutURI $ oxAuthLogoutSessionRequired $ oxdId $ oxAuthAuthorizedOrigins $ tknBndCnf $ oxAccessTokenAsJwt $ oxAccessTokenSigningAlg $ oxAccessTokenLifetime $ oxSoftwareId $ oxSoftwareVersion $ oxSoftwareStatement $ oxRptAsJwt $ oxAttributes $ oxAuthBackchannelTokenDeliveryMode $ oxAuthBackchannelClientNotificationEndpoint $ oxAuthBackchannelAuthenticationRequestSigningAlg $ oxAuthBackchannelUserCodeParameter ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.18 NAME 'oxAuthCustomScope' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( defaultScope $ description $ displayName $ inum $ oxScopeType $ oxAuthClaim $ oxScriptDn $ oxAuthGroupClaims $ oxId $ oxIconUrl $ oxUmaPolicyScriptDn $ oxAttributes $ exp $ del ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.19 NAME 'oxAuthSessionId' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( oxId $ sid $ creationDate $ exp $ del $ oxLastAccessTime $ oxAuthUserDN $ authnTime $ oxState $ oxSessionState $ oxAuthPermissionGranted $ oxAsJwt $ oxJwt $ oxAuthPermissionGrantedMap $ oxInvolvedClients $ oxAuthSessionAttribute ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.20 NAME 'oxAuthConfiguration' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( ou $ oxAuthConfDynamic $ oxAuthConfErrors $ oxAuthConfStatic $ oxAuthConfWebKeys $ oxRevision $ oxWebKeysSettings ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.21 NAME 'oxTrustConfiguration' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( ou $ oxTrustConfApplication $ oxTrustConfCacheRefresh $ oxRevision $ oxTrustConfImportPerson $ oxTrustConfAttributeResolver ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.22 NAME 'oxApplicationConfiguration' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( ou $ oxConfApplication $ oxRevision ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.23 NAME 'oxUmaResource' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( displayName $ inum $ owner $ oxAssociatedClient $ oxAuthUmaScope $ oxFaviconImage $ oxGroup $ oxId $ oxResource $ oxRevision $ oxType $ oxScopeExpression $ iat $ exp $ del $ description ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.24 NAME 'oxUmaResourcePermission' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( exp $ del $ oxAuthUmaScope $ oxConfigurationCode $ oxResourceSetId $ oxAttributes $ oxTicket $ oxStatus ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.25 NAME 'oxAuthGrant' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( grtId $ iat ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.26 NAME 'token' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( authnTime $ authzCode $ iat $ exp $ del $ grtId $ grtTyp $ jwtReq $ nnc $ scp $ tknCde $ tknTyp $ usrId $ clnId $ acr $ uuid $ chlng $ chlngMth $ clms $ ssnId $ attr $ tknBndCnf ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.27 NAME 'oxAuthUmaRPT' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( authnTime $ clnId $ iat $ exp $ del $ tknCde $ usrId $ oxUmaPermission $ uuid $ ssnId ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.28 NAME 'oxLiteralNode' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( literalBinaryValue $ literalValue $ organizationalOwner $ owner $ targetRelationalXdiStatement $ x $ xdiStatement $ xri ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.29 NAME 'oxProxConfiguration' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( ou $ oxProxConf $ oxScriptDn ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.30 NAME 'oxProxOp' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( c $ displayName $ inum $ l $ oxDomain $ oxId $ oxX509PEM $ oxX509URL ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.31 NAME 'oxProxClient' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( displayName $ inum $ oxProxyClaimMapping $ oxProxyScope $ oxProxyToOpClientMapping ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.32 NAME 'oxProxAccessToken' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( iat $ exp $ del $ oxProxyAccessToken $ oxProxyClientId ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.33 NAME 'oxScript' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( inum $ oxScript $ oxScriptType ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.34 NAME 'oxPushApplication' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( displayName $ oxId $ oxName $ oxPushApplicationConf ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.35 NAME 'oxPushDevice' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( oxAuthUserId $ oxId $ oxPushApplication $ oxPushDeviceConf $ oxType ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.36 NAME 'oxCustomScript' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( inum $ displayName $ description $ oxScript $ oxScriptType $ programmingLanguage $ oxModuleProperty $ oxConfigurationProperty $ oxLevel $ oxRevision $ oxEnabled $ oxScriptError $ oxAlias ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.37 NAME 'oxDeviceRegistration' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( oxId $ displayName $ description $ oxDeviceKeyHandle $ oxDeviceHashCode $ oxApplication $ oxDeviceRegistrationConf $ oxDeviceNotificationConf $ oxNickName $ oxDeviceData $ oxCounter $ oxStatus $ del $ exp $ personInum $ creationDate $ oxLastAccessTime $ oxTrustMetaLastModified $ oxTrustMetaLocation $ oxTrustMetaVersion ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.38 NAME 'oxU2fRequest' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( oxId $ oxRequestId $ oxRequest $ oxSessionStateId $ del $ exp $ personInum $ creationDate ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.39 NAME 'oxMetric' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( uniqueIdentifier $ oxStartDate $ oxEndDate $ oxApplicationType $ oxMetricType $ creationDate $ del $ exp $ oxData $ oxHost ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.40 NAME 'oxClientAuthorization' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( oxId $ oxAuthClientId $ oxAuthUserId $ exp $ del $ oxAuthScope ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.41 NAME 'oxSectorIdentifier' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( oxId $ description $ oxAuthRedirectURI $ oxAuthClientId ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.42 NAME 'oxPassportConfiguration' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( ou $ gluuPassportConfiguration $ gluuStatus ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.43 NAME 'oxShibbolethCASProtocolConfiguration' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( ou $ uniqueIdentifier $ inum $ oxConfApplication $ oxRevision ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.44 NAME 'oxAuthUmaPCT' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( clnId $ iat $ exp $ del $ tknCde $ oxClaimValues $ ssnId ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.45 NAME 'cache' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( uuid $ iat $ exp $ del $ dat ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.46 NAME 'oxFido2AuthenticationEntry' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( oxId $ creationDate $ oxSessionStateId $ oxCodeChallenge $ personInum $ oxAuthenticationData $ oxStatus ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.47 NAME 'oxFido2RegistrationEntry' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( oxId $ creationDate $ displayName $ oxSessionStateId $ oxCodeChallenge $ oxCodeChallengeHash $ oxPublicKeyId $ personInum $ oxRegistrationData $ oxDeviceNotificationConf $ oxCounter $ oxStatus ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.48 NAME 'samlAcr' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( parent $ classRef $ inum ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.49 NAME 'gluuOxtrustStat' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( inum $ gluuFreeDiskSpace $ gluuFreeMemory $ gluuFreeSwap $ gluuGroupCount $ gluuIpAddress $ gluuLoadAvg $ gluuPersonCount $ gluuSystemUptime $ uniqueIdentifier ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.50 NAME 'gluuApplicationConfiguration' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( ou $ gluuConfDynamic $ gluuConfStatic $ oxRevision ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.51 NAME 'oxExpiredObject' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( oxId $ dat $ iat $ exp $ oxType ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.52 NAME 'oxRp' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( oxId $ dat ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.53 NAME 'cibaRequest' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( authReqId $ clnId $ usrId $ creationDate $ exp $ oxStatus ) + X-ORIGIN 'Gluu created objectclass' ) + objectClasses: ( 1.3.6.1.4.1.48710.1.4.54 NAME 'jansStatEntry' + SUP ( top ) + STRUCTURAL + MUST ( objectclass ) + MAY ( jansId $ dat $ attr ) + X-ORIGIN 'Gluu created objectclass' ) + +kind: ConfigMap +metadata: + name: oxldif diff --git a/helm/pygluu/kubernetes/terminal/__init__.py b/helm/pygluu/kubernetes/terminal/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/helm/pygluu/kubernetes/terminal/architecture.py b/helm/pygluu/kubernetes/terminal/architecture.py new file mode 100644 index 00000000000..57aa9e56b69 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/architecture.py @@ -0,0 +1,53 @@ +""" +pygluu.kubernetes.terminal.architecture +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for setup of arch backend in terminal installations. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click + + +class PromptArch: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_arch(self): + """Prompts for the kubernetes infrastructure used. + """ + # TODO: This should be auto-detected + if self.settings.get("global.storageClass.provisioner") in (None, ''): + print("|------------------------------------------------------------------|") + print("| Test Environment Deployments |") + print("|------------------------------------------------------------------|") + print("| [1] Microk8s [default] |") + print("| [2] Minikube |") + print("|------------------------------------------------------------------|") + print("| Cloud Deployments |") + print("|------------------------------------------------------------------|") + print("| [3] Amazon Web Services - Elastic Kubernetes Service (Amazon EKS)|") + print("| [4] Google Cloud Engine - Google Kubernetes Engine (GKE) |") + print("| [5] Microsoft Azure (AKS) |") + print("| [6] Digital Ocean [Beta] |") + print("|------------------------------------------------------------------|") + print("| Local Deployments |") + print("|------------------------------------------------------------------|") + print("| [7] Manually provisioned Kubernetes cluster |") + print("|------------------------------------------------------------------|") + + arch_map = { + 1: "microk8s.io/hostpath", + 2: "k8s.io/minikube-hostpath", + 3: "kubernetes.io/aws-ebs", + 4: "kubernetes.io/gce-pd", + 5: "kubernetes.io/azure-disk", + 6: "dobs.csi.digitalocean.com", + 7: "openebs.io/local", + } + choice = click.prompt("Deploy on", default=1) + self.settings.set("global.storageClass.provisioner", arch_map.get(choice, "microk8s.io/hostpath")) diff --git a/helm/pygluu/kubernetes/terminal/aws.py b/helm/pygluu/kubernetes/terminal/aws.py new file mode 100644 index 00000000000..39f5897c447 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/aws.py @@ -0,0 +1,63 @@ +""" +pygluu.kubernetes.terminal.aws +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for aws terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click + +from pygluu.kubernetes.helpers import get_logger + +logger = get_logger("gluu-prompt-aws ") + + +class PromptAws: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_aws_lb(self): + """Prompts for AWS Load balancer information + """ + lb_map = { + 1: "clb", + 2: "nlb", + 3: "alb", + } + + if self.settings.get("installer-settings.aws.lbType") not in lb_map.values(): + print("|-----------------------------------------------------------------|") + print("| AWS Loadbalancer type |") + print("|-----------------------------------------------------------------|") + print("| [1] Classic Load Balancer (CLB) [default] |") + print("| [2] Network Load Balancer (NLB - Alpha) -- Static IP |") + print("| [3] Application Load Balancer (ALB - Alpha) DEV_ONLY |") + print("|-----------------------------------------------------------------|") + + choice = click.prompt("Loadbalancer type", default=1) + self.settings.set("installer-settings.aws.lbType", lb_map.get(choice, "clb")) + if self.settings.get("installer-settings.aws.lbType") == "alb": + logger.info("A prompt later during installation will appear to input the ALB DNS address") + + if self.settings.get("installer-settings.aws.arn.enabled") in (None, ''): + self.settings.set("installer-settings.aws.arn.enabled", click.confirm( + "Are you terminating SSL traffic at LB and using certificate from AWS")) + + if self.settings.get("installer-settings.aws.vpcCidr") in (None, '') and \ + self.settings.get("installer-settings.aws.arn.enabled"): + self.settings.set("installer-settings.aws.vpcCidr", click.prompt( + "Enter VPC CIDR in use for the Kubernetes cluster i.e 192.168.1.1/16", default="0.0.0.0/0" + )) + + if self.settings.get("installer-settings.aws.arn.arnAcmCert") in (None, '') and \ + self.settings.get("installer-settings.aws.arn.enabled"): + # no default means it will try to prompt in loop until user inputs + self.settings.set("installer-settings.aws.arn.arnAcmCert", click.prompt( + "Enter aws-load-balancer-ssl-cert arn quoted ('arn:aws:acm:us-west-2:XXXXXXXX:" + "certificate/XXXXXX-XXXXXXX-XXXXXXX-XXXXXXXX')" + )) diff --git a/helm/pygluu/kubernetes/terminal/backup.py b/helm/pygluu/kubernetes/terminal/backup.py new file mode 100644 index 00000000000..d73fc6653fe --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/backup.py @@ -0,0 +1,56 @@ +""" +pygluu.kubernetes.terminal.backup +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for terminal backup prompt. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click + + +class PromptBackup: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_backup(self): + """Prompt for LDAP and or Couchbase backup strategies + """ + if self.settings.get("global.cnPersistenceType") in ("hybrid", "couchbase"): + if self.settings.get("installer-settings.couchbase.backup.incrementalSchedule") in (None, ''): + self.settings.set("installer-settings.couchbase.backup.incrementalSchedule", click.prompt( + "Please input couchbase backup cron job schedule for incremental backups. " + "This will run backup job every 30 mins by default.", + default="*/30 * * * *", + )) + + if self.settings.get("installer-settings.couchbase.backup.fullSchedule") in (None, ''): + self.settings.set("installer-settings.couchbase.backup.fullSchedule", click.prompt( + "Please input couchbase backup cron job schedule for full backups. " + "This will run backup job on Saturday at 2am", + default="0 2 * * 6", + )) + + if self.settings.get("installer-settings.couchbase.backup.retentionTime") in (None, ''): + self.settings.set("installer-settings.couchbase.backup.retentionTime", click.prompt( + "Please enter the time period in which to retain existing backups. " + "Older backups outside this time frame are deleted", + default="168h", + )) + + if self.settings.get("installer-settings.couchbase.backup.storageSize") in (None, ''): + self.settings.set("installer-settings.couchbase.backup.storageSize", + click.prompt("Size of couchbase backup volume storage", + default="20Gi")) + + elif self.settings.get("global.cnPersistenceType") == "ldap": + if self.settings.get("installer-settings.ldap.backup.fullSchedule") in (None, ''): + self.settings.set("installer-settings.ldap.backup.fullSchedule", click.prompt( + "Please input ldap backup cron job schedule. " + "This will run backup job every 30 mins by default.", + default="*/30 * * * *", + )) diff --git a/helm/pygluu/kubernetes/terminal/cache.py b/helm/pygluu/kubernetes/terminal/cache.py new file mode 100644 index 00000000000..5c3f0bfdc5c --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/cache.py @@ -0,0 +1,41 @@ +""" +pygluu.kubernetes.terminal.cache +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for cache terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click +from pygluu.kubernetes.terminal.redis import PromptRedis + + +class PromptCache: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_cache_type(self): + """Prompt cache type + """ + gluu_cache_map = { + 1: "NATIVE_PERSISTENCE", + 2: "IN_MEMORY", + 3: "REDIS", + } + if self.settings.get("config.configmap.cnCacheType") not in gluu_cache_map.values(): + print("|------------------------------------------------------------------|") + print("| Cache layer |") + print("|------------------------------------------------------------------|") + print("| [1] NATIVE_PERSISTENCE [default] |") + print("| [2] IN_MEMORY |") + print("| [3] REDIS |") + print("|------------------------------------------------------------------|") + choice = click.prompt("Cache layer", default=1) + self.settings.set("config.configmap.cnCacheType", gluu_cache_map.get(choice, "NATIVE_PERSISTENCE")) + if self.settings.get("config.configmap.cnCacheType") == "REDIS": + redis = PromptRedis(self.settings) + redis.prompt_redis() diff --git a/helm/pygluu/kubernetes/terminal/configuration.py b/helm/pygluu/kubernetes/terminal/configuration.py new file mode 100644 index 00000000000..fbc0779f3e2 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/configuration.py @@ -0,0 +1,98 @@ +""" +pygluu.kubernetes.terminal.configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for configuration terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import re +import click + +from pygluu.kubernetes.helpers import get_logger, prompt_password + +logger = get_logger("gluu-prompt-config ") + + +class PromptConfiguration: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + self.config_settings = {"hostname": "", "country_code": "", "state": "", "city": "", "admin_pw": "", + "ldap_pw": "", "email": "", "org_name": "", "redis_pw": ""} + + def prompt_config(self): + """Prompts for generation of configuration layer + """ + check_fqdn_provided = False + + while True: + if self.settings.get("global.fqdn") in (None, '') or check_fqdn_provided: + self.settings.set("global.fqdn", click.prompt("Enter Hostname", default="demoexample.gluu.org")) + + regex_bool = re.match( + '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.){2,}([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9]){2,}$', + # noqa: W605 + self.settings.get("global.fqdn")) + + if regex_bool: + break + else: + check_fqdn_provided = True + logger.error("Input not FQDN structured. Please enter a FQDN with the format demoexample.gluu.org") + + if self.settings.get("config.countryCode") in (None, ''): + self.settings.set("config.countryCode", click.prompt("Enter Country Code", default="US")) + + if self.settings.get("config.state") in (None, ''): + self.settings.set("config.state", click.prompt("Enter State", default="TX")) + + if self.settings.get("config.city") in (None, ''): + self.settings.set("config.city", click.prompt("Enter City", default="Austin")) + + if self.settings.get("config.email") in (None, ''): + self.settings.set("config.email", click.prompt("Enter email", default="support@gluu.org")) + + if self.settings.get("config.orgName") in (None, ''): + self.settings.set("config.orgName", click.prompt("Enter Organization", default="Gluu")) + + if self.settings.get("config.adminPassword") in (None, ''): + self.settings.set("config.adminPassword", prompt_password("Admin GUI")) + + if self.settings.get("config.ldapPassword") in (None, ''): + if self.settings.get("global.cnPersistenceType") in ("hybrid", "ldap"): + self.settings.set("config.ldapPassword", prompt_password("OpenDJ")) + else: + self.settings.set("config.ldapPassword", self.settings.get("config.configmap.cnCouchbasePass")) + + if self.settings.get("global.storageClass.provisioner") in ("microk8s.io/hostpath", "k8s.io/minikube-hostpath"): + self.settings.set("global.isFqdnRegistered", False) + + if self.settings.get("global.isFqdnRegistered") in (None, ''): + self.settings.set("global.isFqdnRegistered", click.confirm("Are you using a globally resolvable FQDN")) + + if self.settings.get("config.migration.enabled") in (None, ''): + self.settings.set("config.migration.enabled", + click.confirm("Are you migrating from the Gluu community edition (VM base)")) + + if self.settings.get("config.migration.enabled"): + if self.settings.get("config.migration.migrationDir") in (None, ''): + self.settings.set("config.migration.migrationDir", + click.prompt("Directory holding the community edition migration files", + default="./ce-migration")) + + if self.settings.get("config.migration.migrationDataFormat") in (None, ''): + while self.settings.get("config.migration.migrationDataFormat") not in ( + "ldif", "couchbase+json", "spanner+avro", "postgresql+json", "mysql+json"): + logger.info("Supported data formats are ldif, couchbase+json, spanner+avro, " + "postgresql+json, and mysql+json ") + self.settings.set("config.migration.migrationDataFormat", + click.prompt("Migration data-format depending on persistence backend. " + "Supported data formats are ldif, couchbase+json, spanner+avro, " + "postgresql+json, and mysql+json ", + default="ldif")) + logger.info("You can mount your FQDN certification and key by placing them inside " + "gluu.crt and gluu.key respectively at the same location pygluu-kubernetes.pyz is at.") diff --git a/helm/pygluu/kubernetes/terminal/confirmsettings.py b/helm/pygluu/kubernetes/terminal/confirmsettings.py new file mode 100644 index 00000000000..a0e1c342987 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/confirmsettings.py @@ -0,0 +1,43 @@ +""" +pygluu.kubernetes.terminal.confirmsettings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for confirming user settings terminal prompt. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" + +import click + + +class PromptConfirmSettings: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def confirm_params(self): + """Formats output of settings from prompts to the user. Passwords are not displayed. + """ + print("{:<1} {:<40} {:<10} {:<35} {:<1}".format('|', 'Setting', '|', 'Value', '|')) + + def iterate_dict(dictionary): + for k, v in dictionary.items(): + if isinstance(v, dict): + iterate_dict(v) + else: + if "Password" not in dictionary[k] and \ + "subjectAlternativeName" not in dictionary[k]: + print("{:<1} {:<40} {:<10} {:<35} {:<1}".format('|', k, '|', v, '|')) + print("{:<1} {:<40} {:<10} {:<35} {:<1}".format('-', 'Setting', '-', 'Value', '-')) + + if click.confirm("Please confirm the above settings"): + self.settings.set("installer-settings.confirmSettings", True) + else: + self.settings.reset_data() + # Prompt for settings again + from pygluu.kubernetes.terminal.prompt import Prompt + initialize_prompts = Prompt() + initialize_prompts.prompt() diff --git a/helm/pygluu/kubernetes/terminal/couchbase.py b/helm/pygluu/kubernetes/terminal/couchbase.py new file mode 100644 index 00000000000..d9cf62343a3 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/couchbase.py @@ -0,0 +1,197 @@ +""" +pygluu.kubernetes.terminal.couchbase +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for couchbase terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" + +from pathlib import Path +import shutil +import base64 + +import click +from pygluu.kubernetes.helpers import get_logger, prompt_password +from pygluu.kubernetes.terminal.backup import PromptBackup +from pygluu.kubernetes.terminal.architecture import PromptArch +from pygluu.kubernetes.terminal.helpers import gather_ip +from pygluu.kubernetes.terminal.namespace import PromptNamespace + +logger = get_logger("gluu-prompt-couchbase") + + +class PromptCouchbase: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + self.backup = PromptBackup(self.settings) + self.arch = PromptArch(self.settings) + self.namespace = PromptNamespace(self.settings) + + def prompt_couchbase(self): + self.arch.prompt_arch() + self.namespace.prompt_gluu_namespace() + + if self.settings.get("global.storageClass.provisioner") \ + not in ("microk8s.io/hostpath", "k8s.io/minikube-hostpath"): + self.backup.prompt_backup() + + if self.settings.get("global.lbIp") in (None, ''): + ip = gather_ip + self.settings.set("global.lbIp", ip) + + if self.settings.get("installer-settings.couchbase.install") in (None, ''): + logger.info("For the following prompt if placed [N] the couchbase is assumed to be" + " installed or remotely provisioned") + self.settings.set("installer-settings.couchbase.install", click.confirm("Install Couchbase", + default=True)) + + if not self.settings.get("installer-settings.couchbase.install"): + if self.settings.get("config.configmap.cnCouchbaseCrt") in (None, ''): + print("Place the Couchbase certificate authority certificate in a file called couchbase.crt at " + "the same location as the installation script.") + print("This can also be found in your couchbase UI Security > Root Certificate") + _ = input("Hit 'enter' or 'return' when ready.") + with open(Path("./couchbase.crt")) as content_file: + ca_crt = content_file.read() + encoded_ca_crt_bytes = base64.b64encode(ca_crt.encode("utf-8")) + encoded_ca_crt_string = str(encoded_ca_crt_bytes, "utf-8") + self.settings.set("config.configmap.cnCouchbaseCrt", encoded_ca_crt_string) + else: + self.settings.set("config.configmap.cnCouchbaseCrt", "") + + self.prompt_override_couchbase_files() + + if self.settings.get("global.storageClass.provisioner") \ + in ("microk8s.io/hostpath", "k8s.io/minikube-hostpath"): + self.settings.set("installer-settings.couchbase.lowResourceInstall", True) + + if self.settings.get("installer-settings.couchbase.lowResourceInstall") in (None, ''): + self.settings.set("installer-settings.couchbase.lowResourceInstall", click.confirm( + "Setup CB nodes using low resources for demo purposes")) + + if not self.settings.get("installer-settings.couchbase.lowResourceInstall") and \ + not self.settings.get("installer-settings.couchbase.customFileOverride") and \ + self.settings.get("installer-settings.couchbase.install"): + self.prompt_couchbase_yaml() + + if self.settings.get("installer-settings.couchbase.namespace") in (None, ''): + self.settings.set("installer-settings.couchbase.namespace", + click.prompt("Please enter a namespace for CB objects.", default="cbns")) + + if self.settings.get("installer-settings.couchbase.clusterName") in (None, ''): + self.settings.set("installer-settings.couchbase.clusterName", + click.prompt("Please enter a cluster name.", default="cbgluu")) + + if self.settings.get("config.configmap.cnCouchbaseUrl") in (None, ''): + self.settings.set("config.configmap.cnCouchbaseUrl", click.prompt( + "Please enter couchbase (remote or local) URL base name", + default=f"{self.settings.get('installer-settings.couchbase.clusterName')}." + f"{self.settings.get('installer-settings.couchbase.namespace')}.svc.cluster.local", + )) + + if self.settings.get("config.configmap.cnCouchbaseBucketPrefix") in (None, ''): + self.settings.set("config.configmap.cnCouchbaseBucketPrefix", click.prompt( + "Please enter a prefix name for all couchbase gluu buckets", + default="gluu" + )) + + if self.settings.get("config.configmap.cnCouchbaseIndexNumReplica") in (None, ''): + self.settings.set("config.configmap.cnCouchbaseIndexNumReplica", click.prompt( + "Please enter the number of replicas per index created. " + "Please note that the number of index nodes must be one greater than the number of replicas. " + "That means if your couchbase cluster only has 2 " + "index nodes you cannot place the number of replicas to be higher than 1.", + default="0", + )) + + if self.settings.get("config.configmap.cnCouchbaseSuperUser") in (None, ''): + self.settings.set("config.configmap.cnCouchbaseSuperUser", + click.prompt("Please enter couchbase superuser username.", default="admin")) + + if self.settings.get("config.configmap.cnCouchbaseSuperUserPassword") in (None, ''): + self.settings.set("config.configmap.cnCouchbaseSuperUserPassword", prompt_password("Couchbase superuser")) + + if self.settings.get("config.configmap.cnCouchbaseUser") in (None, ''): + self.settings.set("config.configmap.cnCouchbaseUser", + click.prompt("Please enter gluu couchbase username.", default="gluu")) + + if self.settings.get("config.configmap.cnCouchbasePassword") in (None, ''): + self.settings.set("config.configmap.cnCouchbasePassword", prompt_password("Couchbase Gluu user")) + + self.find_couchbase_certs_or_set_san_cn() + + def prompt_override_couchbase_files(self): + if self.settings.get("installer-settings.couchbase.customFileOverride") in (None, ''): + self.settings.set("installer-settings.couchbase.customFileOverride", click.confirm( + "Override couchbase-cluster.yaml with a custom couchbase-cluster.yaml", + )) + + if self.settings.get("installer-settings.couchbase.customFileOverride"): + try: + shutil.copy(Path("./couchbase-cluster.yaml"), Path("./couchbase/couchbase-cluster.yaml")) + shutil.copy(Path("./couchbase-buckets.yaml"), Path("./couchbase/couchbase-buckets.yaml")) + shutil.copy(Path("./couchbase-ephemeral-buckets.yaml"), + Path("./couchbase/couchbase-ephemeral-buckets.yaml")) + + except FileNotFoundError: + logger.error("An override option has been chosen but there is a missing couchbase file that " + "could not be found at the current path. Please place the override files under the name" + " couchbase-cluster.yaml, couchbase-buckets.yaml, and couchbase-ephemeral-buckets.yaml" + " in the same directory pygluu-kubernetes.pyz exists ") + raise SystemExit(1) + + def find_couchbase_certs_or_set_san_cn(self): + """Finds couchbase certs inside couchbase_crts-keys folder and if not existent sets couchbase SAN and prompts + for couchbase common name. + """ + custom_cb_ca_crt = Path("./couchbase_crts_keys/ca.crt") + custom_cb_crt = Path("./couchbase_crts_keys/chain.pem") + custom_cb_key = Path("./couchbase_crts_keys/pkey.key") + if not custom_cb_ca_crt.exists() or not custom_cb_crt.exists() and not custom_cb_key.exists(): + if self.settings.get('installer-settings.couchbase.subjectAlternativeName') in (None, ''): + self.settings.set('installer-settings.couchbase.subjectAlternativeName', [ + "*.{}".format(self.settings.get("installer-settings.couchbase.clusterName")), + "*.{}.{}".format(self.settings.get("installer-settings.couchbase.clusterName"), + self.settings.get("installer-settings.couchbase.namespace")), + "*.{}.{}.svc".format(self.settings.get("installer-settings.couchbase.clusterName"), + self.settings.get("installer-settings.couchbase.namespace")), + "*.{}.{}.svc.cluster.local".format(self.settings.get("installer-settings.couchbase.clusterName"), + self.settings.get("installer-settings.couchbase.namespace")), + "{}-srv".format(self.settings.get("installer-settings.couchbase.clusterName")), + "{}-srv.{}".format(self.settings.get("installer-settings.couchbase.clusterName"), + self.settings.get("installer-settings.couchbase.namespace")), + "{}-srv.{}.svc".format(self.settings.get("installer-settings.couchbase.clusterName"), + self.settings.get("installer-settings.couchbase.namespace")), + "*.{}-srv.{}.svc.cluster.local".format( + self.settings.get("installer-settings.couchbase.clusterName"), + self.settings.get("installer-settings.couchbase.namespace")), + "localhost" + ]) + if self.settings.get("installer-settings.couchbase.commonName") in (None, ''): + self.settings.set("installer-settings.couchbase.commonName", + click.prompt("Enter Couchbase certificate common name.", default="Couchbase CA")) + + def prompt_couchbase_yaml(self): + """ + Used to generate couchbase cluster yaml + """ + if not self.settings.get('installer-settings.couchbase.totalNumberOfExpectedUsers'): + self.settings.set('installer-settings.couchbase.totalNumberOfExpectedUsers', + click.prompt("Please enter the number of expected users", default="1000000")) + + if not self.settings.get('installer-settings.couchbase.totalNumberOfExpectedTransactionsPerSec'): + self.settings.set('installer-settings.couchbase.totalNumberOfExpectedTransactionsPerSec', + click.prompt("Expected transactions per second [alpha]", + default=2000)) + + if not self.settings.get('installer-settings.couchbase.volumeType'): + logger.info("GCE GKE Options ('pd-standard', 'pd-ssd')") + logger.info("AWS EKS Options ('gp2', 'io1', 'st1', 'sc1')") + logger.info("Azure Options ('Standard_LRS', 'Premium_LRS', 'StandardSSD_LRS', 'UltraSSD_LRS')") + self.settings.set('installer-settings.couchbase.volumeType', click.prompt("Please enter the volume type.", + default="io1")) diff --git a/helm/pygluu/kubernetes/terminal/distribution.py b/helm/pygluu/kubernetes/terminal/distribution.py new file mode 100644 index 00000000000..e02cd8d63f7 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/distribution.py @@ -0,0 +1,36 @@ +""" +pygluu.kubernetes.terminal.distribution +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for Gluu distribution terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click + + +class PromptDistribution: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_distribution(self): + """Prompt distribution + """ + gluu_distribution_map = { + 1: "default", + 2: "openbanking", + } + if self.settings.get("global.distribution") not in gluu_distribution_map.values() \ + and self.settings.get("global.distribution") in ("None", ''): + print("|------------------------------------------------------------------|") + print("| Gluu Distribution |") + print("|------------------------------------------------------------------|") + print("| [1] default [default] |") + print("| [2] OpenBanking |") + print("|------------------------------------------------------------------|") + choice = click.prompt("Gluu distribution", default=1) + self.settings.set("global.distribution", gluu_distribution_map.get(choice, "default")) diff --git a/helm/pygluu/kubernetes/terminal/gke.py b/helm/pygluu/kubernetes/terminal/gke.py new file mode 100644 index 00000000000..196eec06265 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/gke.py @@ -0,0 +1,36 @@ +""" +pygluu.kubernetes.terminal.gke +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for gke terminal prompt. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click +from pygluu.kubernetes.helpers import exec_cmd + + +class PromptGke: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_gke(self): + """GKE prompts + """ + if not self.settings.get("GMAIL_ACCOUNT"): + self.settings.set("GMAIL_ACCOUNT", click.prompt("Please enter valid email for Google Cloud account")) + + if self.settings.get("APP_VOLUME_TYPE") == 11: + for node_name in self.settings.get("NODES_NAMES"): + for zone in self.settings.get("NODES_ZONES"): + response, error, retcode = exec_cmd("gcloud compute ssh user@{} --zone={} " + "--command='echo $HOME'".format(node_name, zone)) + self.settings.set("GOOGLE_NODE_HOME_DIR", str(response, "utf-8")) + if self.settings.get("GOOGLE_NODE_HOME_DIR"): + break + if self.settings.get("GOOGLE_NODE_HOME_DIR"): + break diff --git a/helm/pygluu/kubernetes/terminal/google.py b/helm/pygluu/kubernetes/terminal/google.py new file mode 100644 index 00000000000..8c5c350fce3 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/google.py @@ -0,0 +1,72 @@ +""" +pygluu.kubernetes.terminal.google +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for spanner terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click +from pathlib import Path +import base64 +import json + + +class PromptGoogle: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_google(self): + """Prompts for spanner ids + """ + if self.settings.get("global.cnPersistenceType") == "spanner": + if not self.settings.get("config,configmap.cnGoogleSpannerInstanceId"): + self.settings.set("config,configmap.cnGoogleSpannerInstanceId", + click.prompt("Please enter the google spanner instance ID.", + default="")) + + if not self.settings.get("config,configmap.cnGoogleSpannerDatabaseId"): + self.settings.set("config,configmap.cnGoogleSpannerDatabaseId", + click.prompt("Please enter the google spanner database ID", + default="")) + # Feature not implemented yet + self.settings.set("installer-settings.google.useSecretManager", False) + if not self.settings.get("installer-settings.google.useSecretManager"): + self.settings.set("installer-settings.google.useSecretManager", + click.confirm("[BETA] Use Google Secret Manager to hold gluu configuration layer. " + "If answered with No, kubernetes secrets will be used", default=False)) + + if self.settings.get("global.cnPersistenceType") == "spanner" or \ + self.settings.get("installer-settings.google.useSecretManager"): + if not self.settings.get("config.configmap.cnGoogleSecretManagerServiceAccount"): + try: + print("Place the google service account json file under the name google_service_account.json. at " + "the same location as the installation script. The service account must have " + "roles/secretmanager.admin to use Google secret manager and/or " + "roles/spanner.databaseUser to use Spanner") + _ = input("Hit 'enter' or 'return' when ready.") + with open(Path("./google_service_account.json")) as content_file: + sa = content_file.read() + encoded_sa_crt_bytes = base64.b64encode(sa.encode("utf-8")) + encoded_sa_crt_string = str(encoded_sa_crt_bytes, "utf-8") + self.settings.set("config.configmap.cnGoogleSecretManagerServiceAccount", encoded_sa_crt_string) + except FileNotFoundError: + print("The google service account json was not found.") + raise SystemExit(1) + + if not self.settings.get("config.configmap.cnGoogleProjectId"): + try: + with open("google_service_account.json", "r") as google_sa: + sa = json.load(google_sa) + self.settings.set("config.configmap.cnGoogleProjectId", sa["project_id"]) + except FileNotFoundError: + print("The google service account json was not found." + "your settings.json.") + if not self.settings.get("config.configmap.cnGoogleProjectId"): + self.settings.set("config.configmap.cnGoogleProjectId", + click.prompt("Please enter the google project ID", + default="")) \ No newline at end of file diff --git a/helm/pygluu/kubernetes/terminal/helm.py b/helm/pygluu/kubernetes/terminal/helm.py new file mode 100644 index 00000000000..33614fc4b95 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/helm.py @@ -0,0 +1,106 @@ +""" +pygluu.kubernetes.terminal.helm +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for helm terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click + + +class PromptHelm: + + def __init__(self, settings): + self.settings = settings + + def prompt_helm(self): + """Prompts for helm installation and returns updated settings. + + :return: + """ + if self.settings.get("installer-settings.releaseName") in ("None", ''): + self.settings.set("installer-settings.releaseName", + click.prompt("Please enter Gluu helm name", default="gluu")) + + # ALPHA-FEATURE: Multi cluster ldap replication + if self.settings.get("global.cnPersistenceType") in ("hybrid", "ldap") and \ + not self.settings.get("opendj.multiCluster.enabled"): + self.settings.set("opendj.multiCluster.enabled", + click.confirm("Are you setting up a multi kubernetes cluster")) + + if self.settings.get("opendj.multiCluster.enabled"): + + if self.settings.get("opendj.multiCluster.serfAdvertiseAddrSuffix") in (None, ''): + self.settings.set("opendj.multiCluster.serfAdvertiseAddrSuffix", + click.prompt("Please enter Serf advertise " + "address suffix. You must be able to " + "resolve this address in your DNS", + default="regional.gluu.org")) + + if self.settings.get("opendj.multiCluster.replicaCount") in (None, ''): + self.settings.set("opendj.multiCluster.replicaCount", + int(click.prompt("Enter the number of opendj statefulsets to create." + " Each will have an advertise address of" + " RELEASE-NAME-opendj-regional-" + "{{statefulset number}}-{Serf address suffix }} ", default="1", + type=click.Choice(["1", "2", "3", "4", "5", "6", "7", "8", "9"])))) + + if self.settings.get("installer-settings.ldap.subsequentCluster") in (None, ''): + self.settings.set("installer-settings.ldap.subsequentCluster", + click.confirm("Is this a subsequent kubernetes cluster " + "( 2nd and above)")) + + if not self.settings.get("opendj.multiCluster.clusterId"): + self.settings.set("opendj.multiCluster.clusterId", + click.prompt("Please enter a cluster ID that distinguishes " + "this cluster from any subsequent clusters. i.e " + "west, east, north, south, test..", default="test")) + + if self.settings.get("opendj.multiCluster.namespaceIntId") in (None, ''): + self.settings.set("opendj.multiCluster.namespaceIntId", + int(click.prompt("Namespace int id. This id needs to be a unique number 0-9 per gluu " + "installation per namespace. Used when gluu is installed in the " + "same kubernetes cluster more than once.", default="0", + type=click.Choice(["0", "1", "2", "3", + "4", "5", "6", "7", "8", "9"])))) + + if not self.settings.get("installer-settings.ldap.multiClusterIds") or \ + not isinstance(self.settings.get("installer-settings.ldap.multiClusterIds"), list): + temp = click.prompt("Please enter the cluster IDs for all other subsequent " + "clusters i.e west, east, north, south, test..seperated by a comma with " + "no quotes , or brackets " + "Forexample, if there was three other clusters ( not including this one)" + " that Gluu will be installed three cluster IDs will be needed. " + "This is to help generate the serf addresses automatically.", + default="dev,stage,prod") + temp = temp.replace(" ", "") + temp_array = temp.split(",") + self.settings.set("installer-settings.ldap.multiClusterIds", list(temp_array)) + + if self.settings.get("opendj.multiCluster.serfPeers") in (None, '') or \ + not isinstance(self.settings.get("opendj.multiCluster.serfPeers"), list): + alist = [] + # temp list to hold all cluster ids including the id of the cluster Gluu is being installed on + cluster_ids = self.settings.get("installer-settings.ldap.multiClusterIds") + if self.settings.get("installer-settings.ldap.clusterId") not in cluster_ids: + cluster_ids.append(self.settings.get("installer-settings.ldap.clusterId")) + for i in range(self.settings.get("installer-settings.ldap.multiClusterIds")): + for cluster_id in cluster_ids: + alist.append(f'{self.settings.get("installer-settings.releaseName")}' + f'-opendj-{cluster_id}-regional-{i}-' + f'{self.settings.get("opendj.multiCluster.serfAdvertiseAddrSuffix")}:3094{i}') + self.settings.set("opendj.multiCluster.serfAdvertiseAddrSuffix", alist) + + if self.settings.get("installer-settings.nginxIngress.releaseName") in (None, '') and \ + self.settings.get("installer-settings.aws.lbType") != "alb": + self.settings.set("installer-settings.nginxIngress.releaseName", + click.prompt("Please enter nginx-ingress helm name", + default="ningress")) + + if self.settings.get("installer-settings.nginxIngress.namespace") in (None, '') and self.settings.get( + "installer-settings.aws.lbType") != "alb": + self.settings.set("installer-settings.nginxIngress.namespace", + click.prompt("Please enter nginx-ingress helm namespace", + default="ingress-nginx")) diff --git a/helm/pygluu/kubernetes/terminal/helpers.py b/helm/pygluu/kubernetes/terminal/helpers.py new file mode 100644 index 00000000000..335269f071c --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/helpers.py @@ -0,0 +1,125 @@ +""" +pygluu.kubernetes.terminal.common +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers for terminal prompt classes + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import base64 +from pathlib import Path + +import click +from pygluu.kubernetes.helpers import get_logger + +logger = get_logger("gluu-prompt-common") + + +def gather_ip(): + """Attempts to detect and return ip automatically. + Also set node names, zones, and addresses in a cloud deployment. + + :return: + """ + from pygluu.kubernetes.kubeapi import Kubernetes + from pygluu.kubernetes.settings import ValuesHandler + import ipaddress + kubernetes = Kubernetes() + settings = ValuesHandler() + logger.info("Determining OS type and attempting to gather external IP address") + ip = "" + + # detect IP address automatically (if possible) + try: + node_ip_list = [] + node_zone_list = [] + node_name_list = [] + node_list = kubernetes.list_nodes().items + + for node in node_list: + node_name = node.metadata.name + node_addresses = kubernetes.read_node(name=node_name).status.addresses + if settings.get("global.storageClass.provisioner") in ("microk8s.io/hostpath", + "k8s.io/minikube-hostpath"): + for add in node_addresses: + if add.type == "InternalIP": + ip = add.address + node_ip_list.append(ip) + else: + for add in node_addresses: + if add.type == "ExternalIP": + ip = add.address + node_ip_list.append(ip) + # Digital Ocean does not provide zone support yet + if settings.get("global.storageClass.provisioner") not in ("dobs.csi.digitalocean.com", + "openebs.io/local"): + node_zone = node.metadata.labels["failure-domain.beta.kubernetes.io/zone"] + node_zone_list.append(node_zone) + node_name_list.append(node_name) + + settings.set("installer-settings.nodes.names", node_name_list) + settings.set("installer-settings.nodes.zones", node_zone_list) + settings.set("installer-settings.nodes.ips", node_ip_list) + + if settings.get("global.storageClass.provisioner") in ("kubernetes.io/aws-ebs", + "kubernetes.io/gce-pd", + "kubernetes.io/azure-disk", + "dobs.csi.digitalocean.com", + "openebs.io/local"): + # Assign random IP. IP will be changed by either the update ip script, GKE external ip or nlb ip + return "22.22.22.22" + + except Exception as e: + logger.error(e) + # prompt for user-inputted IP address + logger.warning("Cannot determine IP address") + ip = click.prompt("Please input the host's external IP address") + + if click.confirm(f"Is this the correct external IP address: {ip}", default=True): + return ip + + while True: + ip = click.prompt("Please input the host's external IP address") + try: + ipaddress.ip_address(ip) + return ip + except ValueError as exc: + # raised if IP is invalid + logger.warning(f"Cannot determine IP address; reason={exc}") + + +def read_file(file): + """ + + @param file: + @return: + """ + try: + _ = input("Hit 'enter' or 'return' when ready.") + with open(Path(file)) as content_file: + content = content_file.read() + encoded_content_bytes = base64.b64encode(content.encode("utf-8")) + encoded_content_string = str(encoded_content_bytes, "utf-8") + return encoded_content_string + except FileNotFoundError: + logger.error(f"File {file} not found.") + raise SystemExit(1) + + +def read_file_bytes(file): + """ + + @param file: + @return: + """ + try: + _ = input("Hit 'enter' or 'return' when ready.") + with open(Path(file), 'rb') as content_file: + content = content_file.read() + encoded_content_bytes = base64.b64encode(content) + encoded_content_string = str(encoded_content_bytes, "utf-8") + return encoded_content_string + except FileNotFoundError: + logger.error(f"File {file} not found.") + raise SystemExit(1) diff --git a/helm/pygluu/kubernetes/terminal/images.py b/helm/pygluu/kubernetes/terminal/images.py new file mode 100644 index 00000000000..81c0525b834 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/images.py @@ -0,0 +1,71 @@ +""" +pygluu.kubernetes.terminal.image +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for image names and tags terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click + + +class PromptImages: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + def __init__(self, settings): + self.settings = settings + + def prompt_image_name_tag(self): + """Manual prompts for image names and tags if changed from default or at a different repository. + """ + + def prompt_and_set_setting(service, image): + repository = f'{image}.image.repository' + tag = f'{image}.image.tag' + settings = self.settings + settings.set(repository, + click.prompt(f"{service} image name", + default=self.settings.get(repository))) + settings.set(tag, + click.prompt(f"{service} image tag", + default=self.settings.get(tag))) + + if self.settings.get("installer-settings.images.edit") in (None, ''): + self.settings.set("installer-settings.images.edit", click.confirm( + "Would you like to manually edit the image source/name and tag")) + + if self.settings.get("installer-settings.images.edit"): + # CASA + if self.settings.get("config.configmap.cnCasaEnabled"): + prompt_and_set_setting("Casa", "casa") + # CONFIG + prompt_and_set_setting("Config", "config") + # CACHE_REFRESH_ROTATE + if self.settings.get("global.cr-rotate.enabled"): + prompt_and_set_setting("CR-rotate", "cr-rotate") + # KEY_ROTATE + if self.settings.get("global.auth-server-key-rotation.enabled"): + prompt_and_set_setting("Key rotate", "auth-server-key-rotation") + # LDAP + if self.settings.get("config.configmap.cnCacheType") in ("hybrid", "ldap"): + prompt_and_set_setting("OpenDJ", "opendj") + # Jackrabbit + prompt_and_set_setting("jackrabbit", "jackrabbit") + # AUTH_SERVER + prompt_and_set_setting("Auth-Server", "auth-server") + # CONFIG_API + if self.settings.get("global.config-api.enabled"): + prompt_and_set_setting("Config-API", "config-api") + # CLIENT_API + if self.settings.get("global.client-api.enabled"): + prompt_and_set_setting("CLIENT_API server", "client-api") + # OXPASSPORT + if self.settings.get("config.configmap.cnPassportEnabled"): + prompt_and_set_setting("oxPassport", "oxpassport") + # OXSHIBBBOLETH + if self.settings.get("global.oxshibboleth.enabled"): + prompt_and_set_setting("oxShibboleth", "oxshibboleth") + # PERSISTENCE + prompt_and_set_setting("Persistence", "persistence") + self.settings.set("installer-settings.images.edit", False) diff --git a/helm/pygluu/kubernetes/terminal/istio.py b/helm/pygluu/kubernetes/terminal/istio.py new file mode 100644 index 00000000000..6ee92a21cc9 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/istio.py @@ -0,0 +1,48 @@ +""" +pygluu.kubernetes.terminal.istio +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for istio terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click +from pygluu.kubernetes.helpers import get_logger + +logger = get_logger("gluu-prompt-istio ") + + +class PromptIstio: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_istio(self): + """Prompt for Istio + """ + if self.settings.get("global.istio.ingress") in (None, '') \ + and self.settings.get("global.storageClass.provisioner") not in \ + ("microk8s.io/hostpath", "k8s.io/minikube-hostpath"): + self.settings.set("global.istio.ingress", click.confirm("[Alpha] Would you like to use " + "Istio Ingress with Gluu ?")) + if self.settings.get("global.istio.ingress"): + self.settings.set("global.istio.enabled", True) + + if self.settings.get("global.istio.enabled") in (None, ''): + logger.info("Please follow https://istio.io/latest/docs/ to learn more.") + logger.info("Istio will auto inject side cars into all pods in Gluus namespace chosen. " + "The label istio-injection=enabled will be added to the namespace Gluu will be installed in " + "if the namespace does not exist. If it does please run " + "kubectl label namespace istio-injection=enabled") + self.settings.set("global.istio.enabled", click.confirm("[Alpha] Would you like to use Istio with Gluu ?")) + + if self.settings.get("global.istio.namespace") in (None, '') and self.settings.get("global.istio.enabled"): + self.settings.set("global.istio.namespace", click.prompt("Istio namespace", + default="istio-system")) + + if self.settings.get("config.configmap.lbAddr") in (None, ''): + self.settings.set("config.configmap.lbAddr", click.prompt("Istio loadbalancer adderss(eks) or " + "ip (gke, aks, digital ocean, local)", default="")) diff --git a/helm/pygluu/kubernetes/terminal/jackrabbit.py b/helm/pygluu/kubernetes/terminal/jackrabbit.py new file mode 100644 index 00000000000..28ddbf3fbc3 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/jackrabbit.py @@ -0,0 +1,72 @@ +""" +pygluu.kubernetes.terminal.jackrabbit +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for jackrabbit terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click + +from pygluu.kubernetes.helpers import get_logger, prompt_password +from pygluu.kubernetes.terminal.postgres import PromptPostgres + +logger = get_logger("gluu-prompt-jackrabbit") + + +class PromptJackrabbit: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + self.postgres = PromptPostgres(self.settings) + + def prompt_jackrabbit(self): + """Prompts for Jackrabbit content repository + """ + if self.settings.get("global.jackrabbit.enabled") in (None, ''): + logger.info("Jackrabbit must be installed. If the following prompt is answered with N it is assumed " + "the jackrabbit content repository is either installed locally or remotely") + self.settings.set("global.jackrabbit.enabled", + click.confirm("Install Jackrabbit content repository", default=True)) + + jackrabbit_cluster_prompt = "Is" + if self.settings.get("global.jackrabbit.enabled"): + if self.settings.get("jackrabbit.storage.size") in (None, ''): + self.settings.set("jackrabbit.storage.size", click.prompt( + "Size of Jackrabbit content repository volume storage", default="4Gi")) + self.settings.set("config.configmap.cnJackrabbitUrl", "http://jackrabbit:8080") + jackrabbit_cluster_prompt = "Enable" + + if self.settings.get("config.configmap.cnJackrabbitUrl") in (None, ''): + self.settings.set("config.configmap.cnJackrabbitUrl", click.prompt("Please enter jackrabbit url.", + default="http://jackrabbit:8080")) + if self.settings.get("config.configmap.cnJackrabbitAdminId") in (None, ''): + self.settings.set("config.configmap.cnJackrabbitAdminId", + click.prompt("Please enter Jackrabbit admin user", default="admin")) + + if self.settings.get("jackrabbit.secrets.cnJackrabbitAdminPassword") in (None, ''): + self.settings.set("jackrabbit.secrets.cnJackrabbitAdminPassword", prompt_password("jackrabbit-admin", 24)) + + if self.settings.get("installer-settings.jackrabbit.clusterMode") in (None, ''): + self.settings.set("installer-settings.jackrabbit.clusterMode", + click.confirm("{} Jackrabbit in cluster mode[beta] " + "Recommended in production" + .format(jackrabbit_cluster_prompt), default=True)) + if self.settings.get("installer-settings.jackrabbit.clusterMode"): + self.postgres.prompt_postgres() + if self.settings.get("config.configmap.cnJackrabbitPostgresUser") in (None, ''): + self.settings.set("config.configmap.cnJackrabbitPostgresUser", + click.prompt("Please enter a user for jackrabbit postgres database", + default="jackrabbit")) + + if self.settings.get("jackrabbit.secrets.cnJackrabbitPostgresPassword") in (None, ''): + self.settings.set("jackrabbit.secrets.cnJackrabbitPostgresPassword", + prompt_password("jackrabbit-postgres")) + + if self.settings.get("config.configmap.cnJackrabbitPostgresDatabaseName") in (None, ''): + self.settings.set("config.configmap.cnJackrabbitPostgresDatabaseName", + click.prompt("Please enter jackrabbit postgres database name", + default="jackrabbit")) diff --git a/helm/pygluu/kubernetes/terminal/ldap.py b/helm/pygluu/kubernetes/terminal/ldap.py new file mode 100644 index 00000000000..be2b2fd7038 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/ldap.py @@ -0,0 +1,45 @@ +""" +pygluu.kubernetes.terminal.ldap +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for ldap terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click + + +class PromptLdap: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_hybrid_ldap_held_data(self): + """Prompts for data held in ldap when hybrid mode is chosen in persistence + """ + hybrid_ldap_map = { + 1: "default", + 2: "user", + 3: "site", + 4: "cache", + 5: "token", + 6: "session", + } + + if self.settings.get("config.configmap.cnPersistenceLdapMapping") not in hybrid_ldap_map.values(): + print("|------------------------------------------------------------------|") + print("| Hybrid [OpenDJ + Couchbase] |") + print("|------------------------------------------------------------------|") + print("| [1] Default |") + print("| [2] User |") + print("| [3] Site |") + print("| [4] Cache |") + print("| [5] Token |") + print("| [6] Session |") + print("|------------------------------------------------------------------|") + + choice = click.prompt("Cache layer", default=1) + self.settings.set("config.configmap.cnPersistenceLdapMapping", hybrid_ldap_map.get(choice, "default")) diff --git a/helm/pygluu/kubernetes/terminal/license.py b/helm/pygluu/kubernetes/terminal/license.py new file mode 100644 index 00000000000..0389248e463 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/license.py @@ -0,0 +1,35 @@ +""" +pygluu.kubernetes.terminal.license +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for terminal license prompt . + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +from pygluu.kubernetes.helpers import get_logger +import click + +logger = get_logger("gluu-prompt-license") + + +class PromptLicense: + + def __init__(self, settings, accept_license=False): + self.settings = settings + if accept_license: + self.settings.set("installer-settings", True) + self.prompt_license() + + def prompt_license(self): + """Prompts user to accept Apache 2.0 license + """ + if not self.settings.get("installer-settings.acceptLicense"): + with open("./LICENSE") as f: + print(f.read()) + + self.settings.set("installer-settings.acceptLicense", + click.confirm("Do you accept the Gluu license stated above")) + if not self.settings.get("installer-settings.acceptLicense"): + logger.info("License not accepted.") + raise SystemExit(1) diff --git a/helm/pygluu/kubernetes/terminal/namespace.py b/helm/pygluu/kubernetes/terminal/namespace.py new file mode 100644 index 00000000000..6b39a0cc371 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/namespace.py @@ -0,0 +1,24 @@ +""" +pygluu.kubernetes.terminal.namespace +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for namespace terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" + +import click + + +class PromptNamespace: + + def __init__(self, settings): + self.settings = settings + + def prompt_gluu_namespace(self): + """Prompt to enable optional services + """ + if self.settings.get("installer-settings.namespace") in (None, ''): + self.settings.set("installer-settings.namespace", + click.prompt("Namespace to deploy Gluu in", default="gluu")) diff --git a/helm/pygluu/kubernetes/terminal/openbanking.py b/helm/pygluu/kubernetes/terminal/openbanking.py new file mode 100644 index 00000000000..a5ed60f18ef --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/openbanking.py @@ -0,0 +1,130 @@ +""" +pygluu.kubernetes.terminal.openbanking +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for terminal openbanking prompts . + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" + +import click +from pygluu.kubernetes.terminal.helpers import read_file, read_file_bytes +from pygluu.kubernetes.helpers import exec_cmd, prompt_password + + +class PromptOpenBanking: + """Prompt is used for prompting users for input used in deploying Gluu OpenBanking distribution. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_openbanking(self): + """Prompts for OpenBanking distribution . + """ + + if self.settings.get("global.cnObExtSigningJwksUri") in ("None", ''): + self.settings.set("global.cnObExtSigningJwksUri", + click.prompt("Open banking external signing jwks uri. Used in SSA Validation.", + default="https://keystore.openbankingtest.org.uk/keystore/openbanking.jwks")) + + if self.settings.get("global.cnObExtSigningJwksCrt") in ("None", ''): + print( + "Place the Open banking external signing jwks AS certificate string in a file named obsigning.pem. " + "Used in SSA Validation. " + " This will be encoded using base64 so please do not encode it.") + encoded_obsigning_pem = read_file("./obsigning.pem") + self.settings.set("global.cnObExtSigningJwksCrt", encoded_obsigning_pem) + + if self.settings.get("global.cnObExtSigningJwksKey") in ("None", ''): + print( + "Place the Open banking external signing jwks AS key string in a file named obsigning.key. Used in " + "SSA Validation. " + " This will be encoded using base64 so please do not encode it.") + encoded_obsigning_pem = read_file("./obsigning.key") + self.settings.set("global.cnObExtSigningJwksKey", encoded_obsigning_pem) + + # TODO: its possible that there is no passphrase for the key, + # and hence the below prompt will always prompt which will affect CI/CD. + # An installer param should be prompted for that case. + if self.settings.get("global.cnObExtSigningJwksKeyPassPhrase") in ("None", ''): + self.settings.set("global.cnObExtSigningJwksKeyPassPhrase", + click.prompt( + "OOpen banking external signing jwks AS key passphrase to unlock provided key.", + default="")) + + if self.settings.get("global.cnObExtSigningAlias") in ("None", ''): + self.settings.set("global.cnObExtSigningAlias", + click.prompt("Open banking external signing AS Alias. " + "This is a kid value.Used in SSA Validation, " + "kid used while encoding a JWT sent to token URL", + default="XkwIzWy44xWSlcWnMiEc8iq9s2G")) + + if self.settings.get("global.cnObStaticSigningKeyKid") in ("None", ''): + self.settings.set("global.cnObStaticSigningKeyKid", + click.prompt("Open banking signing AS kid to force the AS to use a specific signing key", + default="Wy44xWSlcWnMiEc8iq9s2G")) + + if self.settings.get("global.cnObTransportCrt") in ("None", ''): + print( + "Place the Open banking AS transport certificate string in a file named obtransport.pem. Used in SSA " + "Validation. " + " This will be encoded using base64 so please do not encode it.") + encoded_obtransport_pem = read_file("./obtransport.pem") + self.settings.set("global.cnObTransportCrt", encoded_obtransport_pem) + + if self.settings.get("global.cnObTransportKey") in ("None", ''): + print("Place the Open banking AS transport ke string in a file named obtransport.key. Used in SSA " + "Validation. " + " This will be encoded using base64 so please do not encode it.") + encoded_obtransport_key = read_file("./obtransport.key") + self.settings.set("global.cnObTransportKey", encoded_obtransport_key) + + # TODO: its possible that there is no passphrase for the key, + # and hence the below prompt will always prompt which will affect CI/CD. + # An installer param should be prompted for that case. + if self.settings.get("global.cnObTransportKeyPassPhrase") in ("None", ''): + self.settings.set("global.cnObTransportKeyPassPhrase", + click.prompt("Open banking AS transport key passphrase to unlock AS transport key.", + default="")) + + if self.settings.get("global.cnObTransportAlias") in ("None", ''): + self.settings.set("global.cnObTransportAlias", + click.prompt("Open banking transport Alias used inside the JVM", + default="OpenBankingAsTransport")) + + if self.settings.get("installer-settings.openbanking.hasCnObTransportTrustStore") in ("None", ''): + self.settings.set("installer-settings.openbanking.hasCnObTransportTrustStore", + click.confirm("Do you have the Open banking AS transport truststore crt. " + "This is normally generated from the OB issuing CA, " + "OB Root CA and Signing CA.", + default=False)) + + if self.settings.get("global.cnObTransportTrustStore") in ("None", ''): + if self.settings.get("installer-settings.openbanking.hasCnObTransportTrustStore"): + print("Place the Open banking AS transport truststore p12 in a file " + "named obtransporttruststore.p12. Used in SSA " + "Validation. " + " This will be encoded using base64 so please do not encode it.") + encoded_transport_truststore_pem = read_file_bytes("./obtransporttruststore.p12") + self.settings.set("global.cnObTransportTrustStore", encoded_transport_truststore_pem) + else: + print("Place the Open banking issuing CA, OB Root CA and Signing CA string in one file " + "named obcas.pem. Example command: cat obissuingca.pem obrootca.pem obsigningca.pem > obcas.pem " + "This will be used to generate the ob transport truststore p12 file " + " This will be encoded using base64 so please do not encode it.") + # check file is there + read_file("./obcas.pem") + if self.settings.get("installer-settings.openbanking.cnObTransportTrustStoreP12password") in ("None", ''): + self.settings.set("installer-settings.openbanking.cnObTransportTrustStoreP12password", + prompt_password("Open Banking CAs")) + try: + stdout, stderr, retcode = exec_cmd( + f'keytool -importcert -file obcas.pem -keystore ob-transport-truststore.p12 -noprompt ' + f'-alias obkeystore ' + f'-storepass {self.settings.get("installer-settings.openbanking.cnObTransportTrustStoreP12password")}') + except FileNotFoundError: + print("Please install keytool.") + encoded_transport_truststore_pem = read_file_bytes("./ob-transport-truststore.p12") + self.settings.set("global.cnObTransportTrustStore", encoded_transport_truststore_pem) diff --git a/helm/pygluu/kubernetes/terminal/optionalservices.py b/helm/pygluu/kubernetes/terminal/optionalservices.py new file mode 100644 index 00000000000..c1dcde8e51b --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/optionalservices.py @@ -0,0 +1,69 @@ +""" +pygluu.kubernetes.terminal.optionalservices +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for optional services terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" + +import click + + +class PromptOptionalServices: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_optional_services(self): + if self.settings.get("global.cr-rotate.enabled") in (None, ''): + self.settings.set("global.cr-rotate.enabled", click.confirm("Deploy Cr-Rotate")) + + if self.settings.get("global.auth-server-key-rotation.enabled") in (None, ''): + self.settings.set("global.auth-server-key-rotation.enabled", click.confirm("Deploy Key-Rotation")) + + if self.settings.get("global.auth-server-key-rotation.enabled"): + if self.settings.get("auth-server-key-rotation.keysLife") in (None, ''): + self.settings.set("auth-server-key-rotation.keysLife", + click.prompt("Auth-Server keys life in hours", default=48)) + + if self.settings.get("config.configmap.cnPassportEnabled") in (None, ''): + self.settings.set("config.configmap.cnPassportEnabled", click.confirm("Deploy Passport")) + + if self.settings.get("global.oxshibboleth.enabled") in (None, ''): + self.settings.set("global.oxshibboleth.enabled", click.confirm("Deploy Shibboleth SAML IDP")) + + if self.settings.get("config.configmap.cnCasaEnabled") in (None, ''): + self.settings.set("config.configmap.cnCasaEnabled", click.confirm("Deploy Casa")) + if self.settings.get("config.configmap.cnCasaEnabled"): + self.settings.set("global.client-api.enabled", True) + + if self.settings.get("global.fido2.enabled") in (None, ''): + self.settings.set("global.fido2.enabled", click.confirm("Deploy fido2")) + + if self.settings.get("global.config-api.enabled") in (None, ''): + self.settings.set("global.config-api.enabled", click.confirm("Deploy Config API")) + + if self.settings.get("global.scim.enabled") in (None, ''): + self.settings.set("global.scim.enabled", click.confirm("Deploy scim")) + + if self.settings.get("global.scim.enabled") in (None, ''): + self.settings.set("config.configmap.cnScimProtectionMode", + click.prompt("SCIM Protection mode", default="OAUTH", + type=click.Choice(["OAUTH", "TEST", "UMA"]))) + + if self.settings.get("global.client-api.enabled") in (None, ''): + self.settings.set("global.client-api.enabled", click.confirm("Deploy Client API")) + + if self.settings.get("global.client-api.enabled"): + if self.settings.get("config.configmap.cnClientApiApplicationCertCn") in (None, ''): + self.settings.set("config.configmap.cnClientApiApplicationCertCn", + click.prompt("Client API application keystore name", + default="client-api")) + if self.settings.get("config.configmap.cnClientApiAdminCertCn") in (None, ''): + self.settings.set("config.configmap.cnClientApiAdminCertCn", + click.prompt("Client API admin keystore name", + default="client-api")) diff --git a/helm/pygluu/kubernetes/terminal/persistencebackend.py b/helm/pygluu/kubernetes/terminal/persistencebackend.py new file mode 100644 index 00000000000..8f43916f6b6 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/persistencebackend.py @@ -0,0 +1,47 @@ +""" +pygluu.kubernetes.terminal.persistencebackend +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for persistence backend terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click + + +class PromptPersistenceBackend: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_persistence_backend(self): + """Prompts for persistence backend layer + """ + persistence_map = { + 1: "ldap", + 2: "couchbase", + 3: "hybrid", + 4: "spanner", + 5: "sql" + } + + if self.settings.get("global.cnPersistenceType") not in persistence_map.values(): + print("|------------------------------------------------------------------|") + print("| Persistence layer |") + print("|------------------------------------------------------------------|") + print("| [1] OpenDJ [default] |") + print("| [2] Couchbase |") + print("| [3] Hybrid(OpenDJ + Couchbase) |") + print("| [4] Google Spanner |") + print("| [5] SQL(MySQL or PostgreSQL) |") + print("|------------------------------------------------------------------|") + + choice = click.prompt("Persistence layer", default=1) + self.settings.set("global.cnPersistenceType", persistence_map.get(choice, "ldap")) + + self.settings.set("global.opendj.enabled", False) + if self.settings.get("global.cnPersistenceType") == "ldap": + self.settings.set("global.opendj.enabled", True) diff --git a/helm/pygluu/kubernetes/terminal/postgres.py b/helm/pygluu/kubernetes/terminal/postgres.py new file mode 100644 index 00000000000..a3f94839510 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/postgres.py @@ -0,0 +1,58 @@ +""" +pygluu.kubernetes.terminal.postgres +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for postgres terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" + +import click + + +class PromptPostgres: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_postgres(self): + """Prompts for Postgres. + """ + if not self.settings.get("installer-settings.postgres.install"): + self.settings.set("installer-settings.postgres.install", + click.confirm("For the following prompt if N is placed " + "Postgres is assumed to be" + " installed or remotely provisioned. " + "Install Bitnami Postgres chart?", + default=True)) + if self.settings.get("installer-settings.postgres.install"): + if not self.settings.get("installer-settings.postgres.namespace"): + namespace = click.prompt("Please enter a namespace for postgres.", default="postgres") + self.settings.set("installer-settings.postgres.namespace", namespace) + + self.settings.set("config.configmap.cnSqlDbHost", + f"postgresql.{self.settings.get('installer-settings.postgres.namespace')}." + f"svc.cluster.local") + + self.settings.set("config.configmap.cnJackrabbitPostgresHost", + f"postgresql.jackrabbit{self.settings.get('installer-settings.postgres.namespace')}." + f"svc.cluster.local") + + if not self.settings.get("config.configmap.cnSqlDbHost"): + url = click.prompt( + "Please enter postgres (remote or local) " + "URL base name.", + default=f"postgresql.{self.settings.get('installer-settings.postgres.namespace')}.svc.cluster.local", + ) + self.settings.set("config.configmap.cnSqlDbHost", url) + + if not self.settings.get("config.configmap.cnJackrabbitPostgresHost"): + url = click.prompt( + "Please enter postgres (remote or local) " + "URL base name. If postgres is to be installed", + default=f"postgresql.jackrabbit{self.settings.get('installer-settings.postgres.namespace')}.svc.cluster.local", + ) + self.settings.set("config.configmap.cnJackrabbitPostgresHost", url) diff --git a/helm/pygluu/kubernetes/terminal/prompt.py b/helm/pygluu/kubernetes/terminal/prompt.py new file mode 100644 index 00000000000..5cad0636c55 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/prompt.py @@ -0,0 +1,236 @@ +""" +pygluu.kubernetes.terminal.prompt +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to initialize all terminal prompts to +interact with user's inputs for terminal installations. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" + +from pygluu.kubernetes.settings import ValuesHandler +from pygluu.kubernetes.terminal.confirmsettings import PromptConfirmSettings +from pygluu.kubernetes.terminal.volumes import PromptVolumes +from pygluu.kubernetes.terminal.configuration import PromptConfiguration +from pygluu.kubernetes.terminal.jackrabbit import PromptJackrabbit +from pygluu.kubernetes.terminal.istio import PromptIstio +from pygluu.kubernetes.terminal.replicas import PromptReplicas +from pygluu.kubernetes.terminal.couchbase import PromptCouchbase +from pygluu.kubernetes.terminal.architecture import PromptArch +from pygluu.kubernetes.terminal.namespace import PromptNamespace +from pygluu.kubernetes.terminal.optionalservices import PromptOptionalServices +from pygluu.kubernetes.terminal.testenv import PromptTestEnvironment +from pygluu.kubernetes.terminal.aws import PromptAws +from pygluu.kubernetes.terminal.helpers import gather_ip +from pygluu.kubernetes.terminal.persistencebackend import PromptPersistenceBackend +from pygluu.kubernetes.terminal.ldap import PromptLdap +from pygluu.kubernetes.terminal.images import PromptImages +from pygluu.kubernetes.terminal.cache import PromptCache +from pygluu.kubernetes.terminal.backup import PromptBackup +from pygluu.kubernetes.terminal.license import PromptLicense +from pygluu.kubernetes.terminal.version import PromptVersion +from pygluu.kubernetes.terminal.sql import PromptSQL +from pygluu.kubernetes.terminal.google import PromptGoogle +from pygluu.kubernetes.terminal.openbanking import PromptOpenBanking +from pygluu.kubernetes.terminal.distribution import PromptDistribution +from pygluu.kubernetes.terminal.helm import PromptHelm +from pathlib import Path + + +class Prompt: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self): + self.settings = ValuesHandler() + + def load_settings(self): + self.settings = ValuesHandler() + self.settings.store_override_file() + + def license(self): + self.load_settings() + PromptLicense(self.settings) + + def versions(self): + self.load_settings() + PromptVersion(self.settings) + + def arch(self): + self.load_settings() + arch = PromptArch(self.settings) + arch.prompt_arch() + + def namespace(self): + self.load_settings() + namespace = PromptNamespace(self.settings) + namespace.prompt_gluu_namespace() + + def optional_services(self): + self.load_settings() + optional_services = PromptOptionalServices(self.settings) + optional_services.prompt_optional_services() + + def jackrabbit(self): + self.load_settings() + jackrabbit = PromptJackrabbit(self.settings) + jackrabbit.prompt_jackrabbit() + + def istio(self): + self.load_settings() + istio = PromptIstio(self.settings) + istio.prompt_istio() + + def test_enviornment(self): + self.load_settings() + test_environment = PromptTestEnvironment(self.settings) + if not self.settings.get("global.cloud.testEnviroment") and \ + self.settings.get("global.storageClass.provisioner") not in ("microk8s.io/hostpath", + "k8s.io/minikube-hostpath"): + test_environment.prompt_test_environment() + + def network(self): + if self.settings.get("global.lbIp") in ('None', ''): + ip = gather_ip() + self.load_settings() + self.settings.set("global.lbIp", ip) + + if "aws" in self.settings.get("installer-settings.volumeProvisionStrategy") and \ + not self.settings.get("global.istio.enabled"): + aws = PromptAws(self.settings) + aws.prompt_aws_lb() + + def persistence_backend(self): + self.load_settings() + persistence_backend = PromptPersistenceBackend(self.settings) + persistence_backend.prompt_persistence_backend() + + def ldap(self): + self.load_settings() + if self.settings.get("global.cnPersistenceType") == "hybrid": + ldap = PromptLdap(self.settings) + ldap.prompt_hybrid_ldap_held_data() + + def volumes(self): + self.load_settings() + volumes = PromptVolumes(self.settings) + if self.settings.get("global.cnPersistenceType") in ("hybrid", "ldap") or \ + self.settings.get("global.jackrabbit.enabled"): + volumes.prompt_volumes() + volumes.prompt_storage() + + def couchbase(self): + self.load_settings() + couchbase = PromptCouchbase(self.settings) + if self.settings.get("global.cnPersistenceType") in ("hybrid", "couchbase"): + couchbase.prompt_couchbase() + + def cache(self): + self.load_settings() + cache = PromptCache(self.settings) + cache.prompt_cache_type() + + def backup(self): + self.load_settings() + if self.settings.get("global.storageClass.provisioner") not in ("microk8s.io/hostpath", + "k8s.io/minikube-hostpath"): + backup = PromptBackup(self.settings) + backup.prompt_backup() + + def configuration(self): + self.load_settings() + configuration = PromptConfiguration(self.settings) + configuration.prompt_config() + + def images(self): + self.load_settings() + images = PromptImages(self.settings) + images.prompt_image_name_tag() + + def replicas(self): + self.load_settings() + replicas = PromptReplicas(self.settings) + replicas.prompt_replicas() + + def distribution(self): + self.load_settings() + dist = PromptDistribution(self.settings) + dist.prompt_distribution() + + def helm(self): + self.load_settings() + helm = PromptHelm(self.settings) + helm.prompt_helm() + + def openbanking(self): + self.load_settings() + if self.settings.get("global.distribution") == "openbanking": + # Disable all optional services from openbanking distribution + self.settings.set("global.cr-rotate.enabled", False) + self.settings.set("global.auth-server-key-rotation.enabled", False) + self.settings.set("config.configmap.cnPassportEnabled", False) + self.settings.set("global.oxshibboleth.enabled", False) + self.settings.set("config.configmap.cnCasaEnabled", False) + self.settings.set("global.client-api.enabled", False) + self.settings.set("global.fido2.enabled", False) + self.settings.set("global.scim.enabled", False) + self.settings.set("installer-settings.volumeProvisionStrategy", "microk8sDynamic") + # Jackrabbit might be enabled for this distribution later + self.settings.set("global.jackrabbit.enabled", False) + ob = PromptOpenBanking(self.settings) + ob.prompt_openbanking() + + def sql(self): + self.load_settings() + if self.settings.get("global.cnPersistenceType") == "sql": + spanner = PromptSQL(self.settings) + spanner.prompt_sql() + + def google(self): + self.load_settings() + if self.settings.get("global.cnPersistenceType") == "spanner": + spanner = PromptGoogle(self.settings) + spanner.prompt_google() + + def confirm_settings(self): + self.load_settings() + if not self.settings.get("installer-settings.confirmSettings"): + confirm_settings = PromptConfirmSettings(self.settings) + confirm_settings.confirm_params() + + def prompt(self): + """Main property: called to setup all prompts and returns prompts in settings file. + + :return: + """ + # Check if override file by customer exists if not empty the new one + if not Path("./override-values.yaml").exists(): + self.settings.reset_data() + self.license() + self.versions() + self.arch() + self.distribution() + self.openbanking() + self.namespace() + self.optional_services() + if self.settings.get("global.distribution") != "openbanking": + self.jackrabbit() + self.istio() + self.test_enviornment() + self.network() + self.persistence_backend() + self.ldap() + if self.settings.get("global.distribution") != "openbanking": + self.volumes() + self.sql() + self.google() + self.couchbase() + self.cache() + self.backup() + self.configuration() + self.images() + self.replicas() + self.helm() + self.confirm_settings() + self.settings.remove_empty_keys() diff --git a/helm/pygluu/kubernetes/terminal/redis.py b/helm/pygluu/kubernetes/terminal/redis.py new file mode 100644 index 00000000000..7ffaa55c256 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/redis.py @@ -0,0 +1,58 @@ +""" +pygluu.kubernetes.terminal.redis +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for terminal redis prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click + +from pygluu.kubernetes.helpers import get_logger, prompt_password + +logger = get_logger("gluu-prompt-redis ") + + +class PromptRedis: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_redis(self): + """Prompts for Redis + """ + if self.settings.get("config.configmap.cnRedisType") in (None, ''): + logger.info("STANDALONE, CLUSTER") + self.settings.set("config.configmap.cnRedisType", click.prompt("Please enter redis type", default="CLUSTER")) + + if self.settings.get("installer-settings.redis.install"): + logger.info("For the following prompt if placed [N] the Redis is assumed to be" + " installed or remotely provisioned") + self.settings.set("installer-settings.redis.install", click.confirm("Install Redis using Bitnami helm chart")) + + if self.settings.get("installer-settings.redis.install"): + if self.settings.get("installer-settings.redis.namespace") in (None, ''): + namespace = click.prompt("Please enter a namespace for Redis cluster", default="gluu-redis-cluster") + self.settings.set("installer-settings.redis.namespace", namespace) + + if self.settings.get("config.redisPassword") in (None, ''): + self.settings.set("config.redisPassword", prompt_password("Redis")) + + if self.settings.get("config.configmap.cnRedisUrl") in (None, ''): + if self.settings.get("installer-settings.redis.install"): + redis_url_prompt = "redis-cluster.{}.svc.cluster.local:6379".format( + self.settings.get("installer-settings.redis.namespace")) + else: + logger.info( + "Redis URL can be : redis-cluster.gluu-redis-cluster.svc.cluster.local:6379 in a redis deployment") + logger.info("Redis URL using AWS ElastiCach [Configuration Endpoint]: " + "clustercfg.testing-redis.icrbdv.euc1.cache.amazonaws.com:6379") + logger.info("Redis URL using Google MemoryStore : :6379") + redis_url_prompt = click.prompt( + "Please enter redis URL. If you are deploying redis", + default="redis-cluster.gluu-redis-cluster.svc.cluster.local:6379", + ) + self.settings.set("config.configmap.cnRedisUrl", redis_url_prompt) diff --git a/helm/pygluu/kubernetes/terminal/replicas.py b/helm/pygluu/kubernetes/terminal/replicas.py new file mode 100644 index 00000000000..b32436d6eb4 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/replicas.py @@ -0,0 +1,52 @@ +""" +pygluu.kubernetes.terminal.replicas +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for terminal replicas prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click + + +class PromptReplicas: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_replicas(self): + """Prompt number of replicas for Gluu apps + """ + if self.settings.get("auth-server.replicas") in (None, ''): + self.settings.set("auth-server.replicas", click.prompt("Number of Auth-Server replicas", default=1)) + + if self.settings.get("global.config-api.enabled") and self.settings.get("config-api.replicas") in (None, ''): + self.settings.set("config-api.replicas", click.prompt("Number of configAPI replicas", default=1)) + + if self.settings.get("global.fido2.enabled") and self.settings.get("fido2.replicas") in (None, ''): + self.settings.set("fido2.replicas", click.prompt("Number of fido2 replicas", default=1)) + + if self.settings.get("global.scim.enabled") and self.settings.get("scim.replicas") in (None, ''): + self.settings.set("scim.replicas", click.prompt("Number of scim replicas", default=1)) + + if self.settings.get("global.cnPersistenceType") in ("hybrid", "ldap") and \ + self.settings.get("opendj.replicas") in (None, ''): + self.settings.set("opendj.replicas", click.prompt("Number of LDAP replicas", default=1)) + + if self.settings.get("global.oxshibboleth.enabled") and \ + self.settings.get("oxshibboleth.replicas") in (None, ''): + self.settings.set("oxshibboleth.replicas", click.prompt("Number of oxShibboleth replicas", default=1)) + + if self.settings.get("config.configmap.cnPassportEnabled") and \ + self.settings.get("oxpassport.replicas") in (None, ''): + self.settings.set("oxpassport.replicas", click.prompt("Number of oxPassport replicas", default=1)) + + if self.settings.get("global.client-api.enabled") and self.settings.get("client-api.replicas") in (None, ''): + self.settings.set("client-api.replicas", click.prompt("Number of client-api replicas", default=1)) + + if self.settings.get("config.configmap.cnCasaEnabled") and self.settings.get("casa.replicas") in (None, ''): + self.settings.set("casa.replicas", click.prompt("Number of Casa replicas", default=1)) + diff --git a/helm/pygluu/kubernetes/terminal/sql.py b/helm/pygluu/kubernetes/terminal/sql.py new file mode 100644 index 00000000000..515dfea4e76 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/sql.py @@ -0,0 +1,80 @@ +""" +pygluu.kubernetes.terminal.sql +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for jackrabbit terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click + +from pygluu.kubernetes.helpers import get_logger, prompt_password + +logger = get_logger("gluu-prompt-sql") + + +class PromptSQL: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_sql(self): + """Prompts for SQL server + """ + sql_dialect = { + 1: "mysql", + 2: "pgsql", + } + + if self.settings.get("config.configmap.cnSqlDbDialect") not in sql_dialect.values(): + print("|------------------------------------------------------------------|") + print("| SQL DIALECT |") + print("|------------------------------------------------------------------|") + print("| [1] MySQL |") + print("| [2] PostgreSQL |") + print("|------------------------------------------------------------------|") + + choice = click.prompt("SQL dialect", default=1) + self.settings.set("config.configmap.cnSqlDbDialect", sql_dialect.get(choice, "mysql")) + + if not self.settings.get("installer-settings.sql.install"): + logger.info( + "Install SQL dialect from Bitnamis charts.If the following prompt is answered with N it is assumed " + "the SQL server is installed remotely or locally by the user." + " A managed service such as Amazon Aurora or CloudSQL should be used in production setups.") + self.settings.set("installer-settings.sql.install", + click.confirm("Install SQL dialect from Bitnamis charts", default=True)) + + if self.settings.get("installer-settings.sql.install"): + self.settings.set("config.configmap.cnSqlDbPort", 3306) + if not self.settings.get("installer-settings.sql.namespace"): + self.settings.set("installer-settings.sql.namespace", + click.prompt("Please enter a namespace for the SQL server", default="sql")) + + self.settings.set("config.configmap.cnSqlDbHost", + f'gluu-mysql.{self.settings.get("installer-settings.sql.namespace")}.svc.cluster.local') + if self.settings.get("config.configmap.cnSqlDbDialect") == "pgsql": + self.settings.set("installer-settings.postgres.install", True) + self.settings.set("config.configmap.cnSqlDbHost", + f'gluu-postgresql.{self.settings.get("installer-settings.sql.namespace")}.svc.cluster.local') + self.settings.set("config.configmap.cnSqlDbPort", 5432) + if not self.settings.get("config.configmap.cnSqlDbHost"): + self.settings.set("config.configmap.cnSqlDbHost", + click.prompt("Please enter SQL (remote or local) URL base name", + default="gluu.sql.svc.cluster.local")) + if not self.settings.get("config.configmap.cnSqlDbPort"): + self.settings.set("config.configmap.cnSqlDbPort", click.prompt("Please enter SQL (remote or local) port " + "number", default=3306)) + if not self.settings.get("config.configmap.cnSqlDbUser"): + self.settings.set("config.configmap.cnSqlDbUser", click.prompt("Please enter a user for Gluu SQL database ", + default="gluu")) + + if not self.settings.get("config.configmap.cnSqldbUserPassword"): + self.settings.set("config.configmap.cnSqldbUserPassword", prompt_password("gluu-db-sql")) + + if not self.settings.get("config.configmap.cnSqlDbName"): + self.settings.set("config.configmap.cnSqlDbName", click.prompt("Please enter Gluu SQL database name", + default="gluu")) diff --git a/helm/pygluu/kubernetes/terminal/testenv.py b/helm/pygluu/kubernetes/terminal/testenv.py new file mode 100644 index 00000000000..d6e7502b360 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/testenv.py @@ -0,0 +1,30 @@ +""" +pygluu.kubernetes.terminal.testenv +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for terminal test environment prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click +from pygluu.kubernetes.helpers import get_logger + +logger = get_logger("gluu-prompt-test-env") + + +class PromptTestEnvironment: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_test_environment(self): + """Prompts for test environment. + """ + logger.info("A test environment means that the installer will strip all resource requirements, " + "and hence will use as much as needed only. The pods are subject to eviction. Please use " + " at least 8GB Ram , 4 CPU, and 50 GB disk.") + if self.settings.get("global.cloud.testEnviroment") in (None, ''): + self.settings.set("global.cloud.testEnviroment", click.confirm("Is this a test environment.")) diff --git a/helm/pygluu/kubernetes/terminal/upgrade.py b/helm/pygluu/kubernetes/terminal/upgrade.py new file mode 100644 index 00000000000..b25bc7ab3d0 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/upgrade.py @@ -0,0 +1,39 @@ +""" +pygluu.kubernetes.terminal.upgrade +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for upgrade terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" + +import click + +from pygluu.kubernetes.helpers import get_supported_versions +from pygluu.kubernetes.terminal.images import PromptImages + + +class PromptUpgrade: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_upgrade(self): + """Prompts for upgrade and returns updated settings. + :return: + """ + versions, version_number = get_supported_versions() + if self.settings.get("installer-settings.upgrade.targetVersion") in (None, ''): + self.settings.set("installer-settings.upgrade.targetVersion", click.prompt( + "Please enter the version to upgrade Gluu to", default=version_number, + )) + + image_names_and_tags = versions.get(self.settings.get("installer-settings.upgrade.targetVersion"), {}) + self.settings.update(image_names_and_tags) + + # reset this config to force image prompt + self.settings.set("installer-settings.image.edit", "") + PromptImages(self.settings).prompt_image_name_tag() diff --git a/helm/pygluu/kubernetes/terminal/version.py b/helm/pygluu/kubernetes/terminal/version.py new file mode 100644 index 00000000000..8d0a3d894c7 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/version.py @@ -0,0 +1,38 @@ +""" +pygluu.kubernetes.terminal.version +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for terminal gluu version prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" +import click + +from pygluu.kubernetes.helpers import get_supported_versions + + +class PromptVersion: + + def __init__(self, settings, version=""): + self.settings = settings + if not self.settings.get("installer-settings.currentVersion"): + self.settings.set("installer-settings.currentVersion", version) + self.prompt_version() + + def prompt_version(self): + """Prompts for Gluu versions + """ + versions, version_number = get_supported_versions() + + if self.settings.get("installer-settings.currentVersion") in (None, ''): + self.settings.set("installer-settings.currentVersion", click.prompt( + "Please enter the current version of Gluu or the version to be installed", + default=version_number, + )) + + image_names_and_tags = versions.get(self.settings.get("installer-settings.currentVersion"), {}) + # override non-empty image name and tag + for k, v in image_names_and_tags.items(): + if self.settings.get(k) in (None, ''): + self.settings.set(k, v) diff --git a/helm/pygluu/kubernetes/terminal/volumes.py b/helm/pygluu/kubernetes/terminal/volumes.py new file mode 100644 index 00000000000..a20f70aa863 --- /dev/null +++ b/helm/pygluu/kubernetes/terminal/volumes.py @@ -0,0 +1,118 @@ +""" +pygluu.kubernetes.terminal.volumes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains helpers to interact with user's inputs for volume terminal prompts. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" + +import click + +from pygluu.kubernetes.helpers import get_logger + +logger = get_logger("gluu-prompt-volumes") + + +class PromptVolumes: + """Prompt is used for prompting users for input used in deploying Gluu. + """ + + def __init__(self, settings): + self.settings = settings + + def prompt_app_volume_type(self): + """Prompts for volume type + """ + gluu_volume_map = { + 1: "microk8sDynamic", + 2: "minikubeDynamic", + 6: "awsOpenEbsHostPathDynamic", + 7: "awsEbsDynamic", + 11: "gkeOpenEbsHostPathDynamic", + 12: "gkePdDynamic", + 16: "aksOpenEbsHostPathDynamic", + 17: "aksPdDynamic", + 21: "doksOpenEbsHostPathDynamic", + 22: "doksPdDynamic", + 26: "localOpenEbsHostPathDynamic" + } + vol_choice = 0 + if self.settings.get("global.storageClass.provisioner") == "kubernetes.io/aws-ebs": + print("|------------------------------------------------------------------|") + print("|Amazon Web Services - Elastic Kubernetes Service (Amazon EKS) |") + print("| MultiAZ - Supported |") + print("|------------------------------------------------------------------|") + print("| [6] OpenEBS Local PV Hostpath (OpenEBS must be installed) |") + print("| [7] EBS volumes dynamically provisioned [default] |") + vol_choice = click.prompt("What type of volume path", default=7) + elif self.settings.get("global.storageClass.provisioner") == "kubernetes.io/gce-pd": + print("|------------------------------------------------------------------|") + print("|Google Cloud Engine - Google Kubernetes Engine |") + print("|------------------------------------------------------------------|") + print("| [11] OpenEBS Local PV Hostpath (OpenEBS must be installed) |") + print("| [12] Persistent Disk dynamically provisioned [default] |") + vol_choice = click.prompt("What type of volume path", default=12) + elif self.settings.get("global.storageClass.provisioner") == "kubernetes.io/azure-disk": + print("|------------------------------------------------------------------|") + print("|Microsoft Azure |") + print("|------------------------------------------------------------------|") + print("| [16] OpenEBS Local PV Hostpath (OpenEBS must be installed) |") + print("| [17] Persistent Disk dynamically provisioned |") + vol_choice = click.prompt("What type of volume path", default=17) + elif self.settings.get("global.storageClass.provisioner") == "dobs.csi.digitalocean.com": + print("|------------------------------------------------------------------|") + print("|Digital Ocean |") + print("|------------------------------------------------------------------|") + print("| [21] OpenEBS Local PV Hostpath (OpenEBS must be installed) |") + print("| [22] Persistent Disk dynamically provisioned |") + vol_choice = click.prompt("What type of volume path", default=22) + elif self.settings.get("global.storageClass.provisioner") == "openebs.io/local": + print("|------------------------------------------------------------------|") + print("|Local Deployment |") + print("|------------------------------------------------------------------|") + print("| [26] OpenEBS Local PV Hostpath |") + print("|------------------------------------------------------------------|") + logger.info("OpenEBS must be installed before") + vol_choice = click.prompt("What type of volume path", default=26) + self.settings.set("installer-settings.volumeProvisionStrategy", gluu_volume_map.get(vol_choice)) + + def prompt_storage(self): + """Prompt for LDAP storage size + """ + if self.settings.get("global.cnPersistenceType") in ("hybrid", "ldap") and self.settings.get( + "opendj.persistence.size") in (None, ''): + self.settings.set("opendj.persistence.size", click.prompt("Size of ldap volume storage", default="4Gi")) + + def prompt_volumes(self): + """Prompts for all info needed for volume creation on cloud or onpremise + """ + + if self.settings.get("global.storageClass.provisioner") and \ + self.settings.get("installer-settings.volumeProvisionStrategy") in (None, ''): + self.prompt_app_volume_type() + + if self.settings.get("installer-settings.volumeProvisionStrategy") == "aksPdDynamic": + logger.info("Azure Options ('Standard_LRS', 'Premium_LRS', 'StandardSSD_LRS', 'UltraSSD_LRS')") + self.settings.set("global.azureStorageAccountType", + click.prompt("Please enter the volume type.", default="StandardSSD_LRS")) + + elif self.settings.get("global.storageClass.provisioner") == "microk8s.io/hostpath": + self.settings.set("installer-settings.volumeProvisionStrategy", "microk8sDynamic") + + elif self.settings.get("global.storageClass.provisioner") == "k8s.io/minikube-hostpath": + self.settings.set("installer-settings.volumeProvisionStrategy", "minikubeDynamic") + + elif self.settings.get("installer-settings.volumeProvisionStrategy") == "awsEbsDynamic": + logger.info("AWS EKS Options ('gp2', 'io1', `io2`, 'st1', 'sc1')") + self.settings.set("global.awsStorageType", + click.prompt("Please enter the volume type.", default="io1")) + + elif self.settings.get("installer-settings.volumeProvisionStrategy") == "gkePdDynamic": + logger.info("GCE GKE Options ('pd-standard', 'pd-ssd')") + self.settings.set("global.gcePdStorageType", + click.prompt("Please enter the volume type.", default="pd-ssd")) + + elif "OpenEbsHostPathDynamic" in self.settings.get("installer-settings.volumeProvisionStrategy"): + self.settings.set("global.storageClass.provisioner", "openebs.io/local") diff --git a/helm/pygluu/kubernetes/yamlparser.py b/helm/pygluu/kubernetes/yamlparser.py new file mode 100644 index 00000000000..a9781e09859 --- /dev/null +++ b/helm/pygluu/kubernetes/yamlparser.py @@ -0,0 +1,114 @@ +""" +pygluu.kubernetes.yamlparser +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +YAML parser. + +License terms and conditions for Gluu Cloud Native Edition: +https://www.apache.org/licenses/LICENSE-2.0 +""" + +from pathlib import Path +import contextlib +import os +from ruamel.yaml import YAML +from ruamel.yaml.comments import CommentedMap +from collections import OrderedDict, Mapping +from pygluu.kubernetes.helpers import get_logger + +logger = get_logger("gluu-yaml-parser ") + + +class Parser(dict): + def __init__(self, filename, check_value=None, check_value_name=None, check_key='kind'): + super().__init__() + self.filename = Path(filename) + self.yaml = YAML() + self.yaml.preserve_quotes = True + self.manifests_dict_list = [] + self.modify_dict = dict + self.tmp_yaml_file = Path("./tmp.yaml") + + if check_value: + if self.filename.exists(): + with open(filename) as file: + manifests_dicts = self.yaml.load_all(file) + for manifest in manifests_dicts: + try: + if manifest[check_key] == check_value: + if check_value_name: + if manifest['metadata']['name'] == check_value_name: + self.modify_dict = manifest + else: + self.manifests_dict_list.append(manifest) + else: + self.modify_dict = manifest + else: + self.manifests_dict_list.append(manifest) + except KeyError: + # Key kind is not found so its the values.yaml for helm which only has one dict item + self.modify_dict = manifest + with open(self.tmp_yaml_file, 'w') as file: + self.yaml.dump(self.modify_dict, file) + + with open(self.tmp_yaml_file) as f: + super(Parser, self).update(self.yaml.load(f) or {}) + + @property + def return_manifests_dict(self): + if self.filename.exists(): + with open(self.filename) as file: + manifests_dicts = self.yaml.load_all(file) + for manifest in manifests_dicts: + self.manifests_dict_list.append(manifest) + + return self.manifests_dict_list + + def __setitem__(self, key, value): + super(Parser, self).__setitem__(key, value) + + def dump_it(self, clean_data=False): + d = self.analyze_ordered_dict_object(self) + if clean_data: + d = self.clean_dict(d) + final_manifest_dict_list = self.manifests_dict_list + [d] + with open(self.filename, "w+") as f: + self.yaml.dump_all(final_manifest_dict_list, f) + with contextlib.suppress(FileNotFoundError): + os.remove(self.tmp_yaml_file) + + def analyze_ordered_dict_object(self, data): + if isinstance(data, OrderedDict) or isinstance(data, dict): + commented_map = CommentedMap() + for k, v in data.items(): + commented_map[k] = self.analyze_ordered_dict_object(v) + return commented_map + return data + + def __delitem__(self, key): + try: + super(Parser, self).__delitem__(key) + except KeyError as e: + logger.error(e) + + def clean_dict(self, data): + if isinstance(data, dict): + return { + k: v + for k, v in ((k, self.clean_dict(v)) for k, v in data.items()) + if v + } + + if isinstance(data, list): + return [v for v in map(self.clean_dict, data) if v] + + if data: + return data + + def update(self, other=None, **kwargs): + if other is not None: + for k, v in other.items() if isinstance(other, Mapping) else other: + self[k] = v + for k, v in kwargs.items(): + self[k] = v + super(Parser, self).update(self) diff --git a/helm/settings_schema.json b/helm/settings_schema.json new file mode 100644 index 00000000000..0473e64fd2c --- /dev/null +++ b/helm/settings_schema.json @@ -0,0 +1,1193 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties" : false, + "properties": { + "ACCEPT_CN_LICENSE": { "$ref": "#/definitions/yes-no-string" }, + "CN_VERSION": { "type": "string" }, + "TEST_ENVIRONMENT": { "$ref": "#/definitions/yes-no-string" }, + "CN_UPGRADE_TARGET_VERSION": { "type": "string" }, + "CN_HELM_RELEASE_NAME": { "type": "string" }, + "NGINX_INGRESS_RELEASE_NAME": { "type": "string" }, + "NGINX_INGRESS_NAMESPACE": { "type": "string" }, + "POSTGRES_NAMESPACE": { "type": "string" }, + "POSTGRES_REPLICAS": { "$ref": "#/definitions/emptiable-number" }, + "POSTGRES_URL": { "type": "string" }, + "USE_ISTIO": { "$ref": "#/definitions/yes-no-string" }, + "USE_ISTIO_INGRESS": { "$ref": "#/definitions/yes-no-string" }, + "ISTIO_SYSTEM_NAMESPACE": { "type": "string" }, + "NODES_IPS": { "type": "array" }, + "NODES_ZONES": { "type": "array" }, + "NODES_NAMES": { "type": "array" }, + "NODE_SSH_KEY": { "type": "string" }, + "HOST_EXT_IP": { "type": "string", "format": "ipv4" }, + "VERIFY_EXT_IP": { "$ref": "#/definitions/yes-no-string" }, + "AWS_LB_TYPE": { "type": "string", "enum": ["clb", "nlb", "alb", ""] }, + "USE_ARN": { "$ref": "#/definitions/yes-no-string"}, + "VPC_CIDR": { "type": "string" }, + "ARN_AWS_IAM": { "type": "string" }, + "LB_ADD": { "type": "string" }, + "REDIS_URL": { "type": "string" }, + "REDIS_TYPE": { "type": "string" }, + "REDIS_PW": { "type": "string" }, + "REDIS_USE_SSL": { "type": "string", "enum": ["true", "false"] }, + "REDIS_SSL_TRUSTSTORE": { "type": "string" }, + "REDIS_SENTINEL_GROUP": { "type": "string" }, + "REDIS_MASTER_NODES": { "$ref": "#/definitions/emptiable-number" }, + "REDIS_NODES_PER_MASTER": { "$ref": "#/definitions/emptiable-number" }, + "REDIS_NAMESPACE": { "type": "string" }, + "INSTALL_REDIS": { "$ref": "#/definitions/yes-no-string" }, + "INSTALL_JACKRABBIT": { "$ref": "#/definitions/yes-no-string" }, + "JACKRABBIT_STORAGE_SIZE": { "type": "string" }, + "JACKRABBIT_URL": { "type": "string" }, + "JACKRABBIT_ADMIN_ID": { "type": "string" }, + "JACKRABBIT_ADMIN_PASSWORD": { "$ref": "#/definitions/password" }, + "JACKRABBIT_CLUSTER": { "type": "string" }, + "JACKRABBIT_PG_USER": { "type": "string" }, + "JACKRABBIT_PG_PASSWORD": { "$ref": "#/definitions/password" }, + "JACKRABBIT_DATABASE": { "type": "string" }, + "DEPLOYMENT_ARCH": { "type": "string", "enum": ["microk8s", "minikube", "eks", "gke", "aks", "do", "local", ""] }, + "PERSISTENCE_BACKEND": { "type": "string", "enum": ["ldap", "couchbase", "hybrid", ""] }, + "INSTALL_COUCHBASE": { "$ref": "#/definitions/yes-no-string" }, + "COUCHBASE_NAMESPACE": { "type": "string" }, + "COUCHBASE_VOLUME_TYPE": { "type": "string" }, + "COUCHBASE_CLUSTER_NAME": { "type": "string" }, + "COUCHBASE_URL": { "type": "string" }, + "COUCHBASE_USER": { "type": "string" }, + "COUCHBASE_BUCKET_PREFIX": { "type": "string" }, + "COUCHBASE_SUPERUSER": { "type": "string" }, + "COUCHBASE_PASSWORD": { "type": "string" }, + "COUCHBASE_SUPERUSER_PASSWORD": { "type": "string" }, + "COUCHBASE_CRT": { "type": "string" }, + "COUCHBASE_CN": { "type": "string" }, + "COUCHBASE_INDEX_NUM_REPLICA": { "type": "string" }, + "COUCHBASE_SUBJECT_ALT_NAME": { "$ref": "#/definitions/emptiable-array" }, + "COUCHBASE_CLUSTER_FILE_OVERRIDE": { "$ref": "#/definitions/yes-no-string" }, + "COUCHBASE_USE_LOW_RESOURCES": { "$ref": "#/definitions/yes-no-string"}, + "COUCHBASE_DATA_NODES": { "type": "string" }, + "COUCHBASE_QUERY_NODES": { "type": "string" }, + "COUCHBASE_INDEX_NODES": { "type": "string" }, + "COUCHBASE_SEARCH_EVENTING_ANALYTICS_NODES": { "type": "string" }, + "COUCHBASE_GENERAL_STORAGE": { "type": "string" }, + "COUCHBASE_DATA_STORAGE": { "type": "string" }, + "COUCHBASE_INDEX_STORAGE": { "type": "string" }, + "COUCHBASE_QUERY_STORAGE": { "type": "string" }, + "COUCHBASE_ANALYTICS_STORAGE": { "type": "string" }, + "COUCHBASE_INCR_BACKUP_SCHEDULE": { "type": "string" }, + "COUCHBASE_FULL_BACKUP_SCHEDULE": { "type": "string" }, + "COUCHBASE_BACKUP_RETENTION_TIME": { "type": "string" }, + "COUCHBASE_BACKUP_STORAGE_SIZE": { "type": "string" }, + "LDAP_BACKUP_SCHEDULE": { "type": "string" }, + "NUMBER_OF_EXPECTED_USERS": { "$ref": "#/definitions/emptiable-number" }, + "EXPECTED_TRANSACTIONS_PER_SEC": { "type": "string" }, + "USING_CODE_FLOW": { "$ref": "#/definitions/yes-no-string" }, + "USING_SCIM_FLOW": { "$ref": "#/definitions/yes-no-string" }, + "USING_RESOURCE_OWNER_PASSWORD_CRED_GRANT_FLOW": { "$ref": "#/definitions/yes-no-string" }, + "DEPLOY_MULTI_CLUSTER": { "type": "string" }, + "HYBRID_LDAP_HELD_DATA": { "type": "string", "enum": ["", "default", "user", "site", "cache", "token"] }, + "LDAP_JACKRABBIT_VOLUME": { "type": "string" }, + "APP_VOLUME_TYPE": { "$ref": "#/definitions/emptiable-number" }, + "LDAP_STATIC_VOLUME_ID": { "type": "string" }, + "LDAP_STATIC_DISK_URI": { "type": "string" }, + "CN_CACHE_TYPE": { "type": "string", "enum": ["IN_MEMORY", "REDIS", "NATIVE_PERSISTENCE", ""]}, + "CN_NAMESPACE": { "type": "string" }, + "CN_FQDN": { "$ref": "#/definitions/fqdn-pattern" }, + "COUNTRY_CODE": { "type": "string" }, + "STATE": { "type": "string" }, + "EMAIL": { "$ref": "#/definitions/email-format" }, + "CITY": { "type": "string" }, + "ORG_NAME": { "type": "string" }, + "GMAIL_ACCOUNT": { "$ref": "#/definitions/email-format" }, + "GOOGLE_NODE_HOME_DIR": { "type": "string" }, + "IS_CN_FQDN_REGISTERED": { "$ref": "#/definitions/yes-no-string" }, + "LDAP_PW": { "$ref": "#/definitions/password" }, + "ADMIN_PW": { "$ref": "#/definitions/password" }, + "CLIENT_API_APPLICATION_KEYSTORE_CN": { "type": "string" }, + "CLIENT_API_ADMIN_KEYSTORE_CN": { "type": "string" }, + "LDAP_STORAGE_SIZE": { "type": "string" }, + "AUTH_SERVER_REPLICAS": { "$ref": "#/definitions/emptiable-number" }, + "OXTRUST_REPLICAS": { "$ref": "#/definitions/emptiable-number" }, + "LDAP_REPLICAS": { "$ref": "#/definitions/emptiable-number" }, + "OXSHIBBOLETH_REPLICAS": { "$ref": "#/definitions/emptiable-number" }, + "OXPASSPORT_REPLICAS": { "$ref": "#/definitions/emptiable-number" }, + "CLIENT_API_REPLICAS": { "$ref": "#/definitions/emptiable-number" }, + "CASA_REPLICAS": { "$ref": "#/definitions/emptiable-number" }, + "FIDO2_REPLICAS": { "$ref": "#/definitions/emptiable-number" }, + "SCIM_REPLICAS": { "$ref": "#/definitions/emptiable-number" }, + "ENABLE_CONFIG_API": { "$ref": "#/definitions/yes-no-string" }, + "ENABLE_OXTRUST_API": { "$ref": "#/definitions/yes-no-string" }, + "ENABLE_OXTRUST_TEST_MODE": { "$ref": "#/definitions/yes-no-string" }, + "ENABLE_CACHE_REFRESH": { "$ref": "#/definitions/yes-no-string" }, + "ENABLE_CLIENT_API": { "$ref": "#/definitions/yes-no-string" }, + "ENABLE_FIDO2": { "$ref": "#/definitions/yes-no-string" }, + "ENABLE_SCIM": { "$ref": "#/definitions/yes-no-string" }, + "ENABLE_OXPASSPORT": { "$ref": "#/definitions/yes-no-string" }, + "ENABLE_OXSHIBBOLETH": { "$ref": "#/definitions/yes-no-string" }, + "ENABLE_CASA": { "$ref": "#/definitions/yes-no-string" }, + "ENABLE_AUTH_SERVER_KEY_ROTATE": { "$ref": "#/definitions/yes-no-string" }, + "ENABLE_OXTRUST_API_BOOLEAN": { "$ref": "#/definitions/emptiable-boolean-string" }, + "ENABLE_OXTRUST_TEST_MODE_BOOLEAN": { "$ref": "#/definitions/emptiable-boolean-string" }, + "ENABLE_OXPASSPORT_BOOLEAN": { "$ref": "#/definitions/emptiable-boolean-string" }, + "ENABLE_CASA_BOOLEAN": { "$ref": "#/definitions/emptiable-boolean-string" }, + "ENABLE_SAML_BOOLEAN": { "$ref": "#/definitions/emptiable-boolean-string" }, + "ENABLED_SERVICES_LIST": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "config", + "auth-server", + "oxtrust", + "persistence", + "jackrabbit", + "cr-rotate", + "auth-server-key-rotation", + "oxpassport", + "oxshibboleth", + "casa", + "fido2", + "scim", + "client-api", + "ldap", + "update-lb-ip" + ] + }, + "uniqueItems": true }, + "AUTH_SERVER_KEYS_LIFE": { "$ref": "#/definitions/emptiable-number"}, + "EDIT_IMAGE_NAMES_TAGS": { "$ref": "#/definitions/yes-no-string" }, + "CASA_IMAGE_NAME": { "type": "string" }, + "CASA_IMAGE_TAG": { "type": "string" }, + "CONFIG_IMAGE_NAME": { "type": "string" }, + "CONFIG_IMAGE_TAG": { "type": "string" }, + "CACHE_REFRESH_ROTATE_IMAGE_NAME": { "type": "string" }, + "CACHE_REFRESH_ROTATE_IMAGE_TAG": { "type": "string" }, + "CERT_MANAGER_IMAGE_NAME": { "type": "string" }, + "CERT_MANAGER_IMAGE_TAG": { "type": "string" }, + "LDAP_IMAGE_NAME": { "type": "string" }, + "LDAP_IMAGE_TAG": { "type": "string" }, + "JACKRABBIT_IMAGE_NAME": { "type": "string" }, + "JACKRABBIT_IMAGE_TAG": { "type": "string" }, + "AUTH_SERVER_IMAGE_NAME": { "type": "string" }, + "AUTH_SERVER_IMAGE_TAG": { "type": "string" }, + "FIDO2_IMAGE_NAME": { "type": "string" }, + "FIDO2_IMAGE_TAG": { "type": "string" }, + "SCIM_IMAGE_NAME": { "type": "string" }, + "SCIM_IMAGE_TAG": { "type": "string" }, + "CLIENT_API_IMAGE_NAME": { "type": "string" }, + "CLIENT_API_IMAGE_TAG": { "type": "string" }, + "OXPASSPORT_IMAGE_NAME": { "type": "string" }, + "OXPASSPORT_IMAGE_TAG": { "type": "string" }, + "OXSHIBBOLETH_IMAGE_NAME": { "type": "string" }, + "OXSHIBBOLETH_IMAGE_TAG": { "type": "string" }, + "OXTRUST_IMAGE_NAME": { "type": "string" }, + "OXTRUST_IMAGE_TAG": { "type": "string" }, + "PERSISTENCE_IMAGE_NAME": { "type": "string" }, + "PERSISTENCE_IMAGE_TAG": { "type": "string" }, + "UPGRADE_IMAGE_NAME": { "type": "string" }, + "UPGRADE_IMAGE_TAG": { "type": "string" }, + "CONFIRM_PARAMS": { "$ref": "#/definitions/yes-no-string" } + }, + "allOf": [ + { "$ref": "#/definitions/cache-refresh-enabled"}, + { "$ref": "#/definitions/auth-server-key-rotate-enabled"}, + { "$ref": "#/definitions/oxpassport-enabled" }, + { "$ref": "#/definitions/oxshibboleth-enabled" }, + { "$ref": "#/definitions/casa-enabled" }, + { "$ref": "#/definitions/fido2-enabled" }, + { "$ref": "#/definitions/scim-enabled" }, + { "$ref": "#/definitions/client-api-enabled" }, + { "$ref": "#/definitions/oxtrust-api-enabled" }, + { "$ref": "#/definitions/oxtrust-test-mode-enabled"}, + { "$ref": "#/definitions/install-jackrabbit-yes" }, + { "$ref": "#/definitions/install-jackrabbit-no" }, + { "$ref": "#/definitions/jackrabbit-cluster-enable" }, + { "$ref": "#/definitions/istio-ingress-yes" }, + { "$ref": "#/definitions/istio-yes" }, + { "$ref": "#/definitions/test-environment" }, + { "$ref": "#/definitions/network-aws" }, + { "$ref": "#/definitions/use-arn-yes" }, + { "$ref": "#/definitions/deployment-arch-gke" }, + { "$ref": "#/definitions/persistence-backend-ldap" }, + { "$ref": "#/definitions/persistence-backend-hybrid"}, + { "$ref": "#/definitions/microk8s-architecture" }, + { "$ref": "#/definitions/minikube-architecture" }, + { "$ref": "#/definitions/eks-architecture" }, + { "$ref": "#/definitions/gke-architecture" }, + { "$ref": "#/definitions/aks-architecture" }, + { "$ref": "#/definitions/do-architecture" }, + { "$ref": "#/definitions/local-architecture" }, + { "$ref": "#/definitions/ldap-volume-identifier" }, + { "$ref": "#/definitions/ldap-disk-uris" }, + { "$ref": "#/definitions/ldap-jackrabbit-volume-on-aks" }, + { "$ref": "#/definitions/ldap-jackrabbit-volume-on-eks" }, + { "$ref": "#/definitions/ldap-jackrabbit-volume-on-gke" }, + { "$ref": "#/definitions/ldap-storage" }, + { "$ref": "#/definitions/couchbase-multi-cluster" }, + { "$ref": "#/definitions/couchbase-persistence-backend" }, + { "$ref": "#/definitions/install-couchbase-yes" }, + { "$ref": "#/definitions/install-couchbase-no" }, + { "$ref": "#/definitions/couchbase-not-use-low-resource" }, + { "$ref": "#/definitions/cache-type-redis" }, + { "$ref": "#/definitions/install-redis-yes" }, + { "$ref": "#/definitions/install-redis-no" }, + { "$ref": "#/definitions/backup-hybrid-couchbase" }, + { "$ref": "#/definitions/backup-ldap" } + ], + "definitions": { + "yes-no-string": { + "anyOf": [ + { + "type": "string", + "enum": ["Y", "N"] + }, + { + "type": "string", + "maxLength": 0 + } + ] + }, + "emptiable-number": { + "anyOf": [ + { + "type": "number", + "minimum": 1 + }, + { + "type": "string", + "maxLength": 0 + } + ] + }, + "emptiable-array": { + "anyOf": [ + { + "type": "array" + }, + { + "type": "string", + "maxLength": 0 + } + ] + }, + "emptiable-boolean-string": { + "anyOf": [ + { + "type": "string", + "enum": ["true", "false", ""] + }, + { + "type": "string", + "maxLength": 0 + } + ] + }, + "string-cannot-empty": { + "type": "string", + "minLength": 1, + "errors": { + "minLength": "Field cannot be empty" + } + }, + "password": { + "anyOf": [ + { + "type": "string", + "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*\\W)[a-zA-Z0-9\\S]{6,}$", + "minLength": 6, + "errors": { + "minLength": "Password minimum 6 character", + "pattern": "Password does not meet requirements. The password must contain one digit, one uppercase letter, one lower case letter and one symbol" + } + }, + { + "type": "string", + "maxLength": 0 + } + ] + }, + "password-pattern": { + "type": "string", + "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*\\W)[a-zA-Z0-9\\S]{6,}$", + "minLength": 6, + "errors": { + "minLength": "Password minimum 6 character", + "pattern": "Password does not meet requirements. The password must contain one digit, one uppercase letter, one lower case letter and one symbol" + } + }, + "email-format": { + "type": "string", + "format": "email" + }, + "fqdn-pattern": { + "anyOf": [ + { + "type": "string", + "pattern": "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.){2,}([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9]){2,}$", + "errors": { + "pattern": "Setting not FQDN structured. Please enter a FQDN with the format demoexample.gluu.org" + } + }, + { + "type": "string", + "maxLength": 0 + } + ] + }, + "cache-refresh-enabled": { + "if": { + "properties": { + "ENABLE_CACHE_REFRESH": { "const": "Y" } + } + }, + "then": { + "properties": { + "ENABLED_SERVICES_LIST": { + "contains": { "enum" : ["cr-rotate"] }, + "errors": { + "contains": "cr-rotate key not found" + } + } + }, + "required": ["ENABLED_SERVICES_LIST"] + } + }, + "auth-server-key-rotate-enabled": { + "if": { + "properties": { + "ENABLE_AUTH_SERVER_KEY_ROTATE": { "const": "Y" } + } + }, + "then": { + "properties": { + "AUTH_SERVER_KEYS_LIFE": { "type": "number", "minimum": 48 }, + "ENABLED_SERVICES_LIST": { + "contains": { "enum" : ["auth-server-key-rotation"] }, + "errors": { + "contains": "auth-server-key-rotation key not found" + } + } + }, + "required": ["ENABLED_SERVICES_LIST", "AUTH_SERVER_KEYS_LIFE"] + } + }, + "oxpassport-enabled": { + "if": { + "properties": { + "ENABLE_OXPASSPORT": { "const": "Y" } + } + }, + "then": { + "properties": { + "ENABLE_OXPASSPORT_BOOLEAN": { "const": "true" }, + "ENABLED_SERVICES_LIST": { + "contains": { "enum" : ["oxpassport"] }, + "errors": { + "contains": "oxpassport key not found" + } + } + }, + "required": ["ENABLE_OXPASSPORT_BOOLEAN", "ENABLED_SERVICES_LIST"] + } + }, + "oxshibboleth-enabled": { + "if": { + "properties": { + "ENABLE_OXSHIBBOLETH": { "const": "Y" } + } + }, + "then": { + "properties": { + "ENABLE_SAML_BOOLEAN": { "const": "true" }, + "ENABLED_SERVICES_LIST": { + "contains": { "enum" : ["oxshibboleth"] }, + "errors": { + "contains": "oxshibboleth key not found" + } + } + }, + "required": ["ENABLE_SAML_BOOLEAN", "ENABLED_SERVICES_LIST"] + } + }, + "casa-enabled": { + "if": { + "properties": { + "ENABLE_CASA": { "const": "Y" } + } + }, + "then": { + "properties": { + "ENABLE_CASA_BOOLEAN": { "const": "true" }, + "ENABLED_SERVICES_LIST": { + "contains": { "enum" : ["casa"] }, + "errors": { + "contains": "casa key not found" + } + }, + "ENABLE_CLIENT_API": { "const": "Y" } + }, + "required": ["ENABLE_CASA_BOOLEAN", "ENABLE_CLIENT_API", "ENABLED_SERVICES_LIST"] + } + }, + "fido2-enabled": { + "if": { + "properties": { + "ENABLE_FIDO2": { "const": "Y" } + } + }, + "then": { + "properties": { + "ENABLED_SERVICES_LIST": { + "contains": { "enum" : ["fido2"] }, + "errors": { + "contains": "fido2 key not found" + } + } + }, + "required": ["ENABLED_SERVICES_LIST"] + } + }, + "scim-enabled": { + "if": { + "properties": { + "ENABLE_SCIM": { "const": "Y" } + } + }, + "then": { + "properties": { + "ENABLED_SERVICES_LIST": { + "contains": { "enum" : ["scim"] }, + "errors": { + "contains": "scim key not found" + } + } + }, + "required": ["ENABLED_SERVICES_LIST"] + } + }, + "client-api-enabled": { + "if": { + "properties": { + "ENABLE_CLIENT_API": { "const": "Y" } + } + }, + "then": { + "properties": { + "ENABLED_SERVICES_LIST": { + "contains": { "enum" : ["client-api"] }, + "errors": { + "contains": "client-api key not found" + } + }, + "CLIENT_API_APPLICATION_KEYSTORE_CN": { "type": "string", "minLength": 3 }, + "CLIENT_API_ADMIN_KEYSTORE_CN": { "type": "string", "minLength": 3 } + }, + "required": ["CLIENT_API_APPLICATION_KEYSTORE_CN", "CLIENT_API_ADMIN_KEYSTORE_CN", "ENABLED_SERVICES_LIST"] + } + }, + "oxtrust-api-enabled": { + "if": { + "properties": { + "ENABLE_OXTRUST_API": { "const": "Y" } + } + }, + "then": { + "properties": { + "ENABLE_OXTRUST_API_BOOLEAN": { "const": "true" }, + "ENABLE_OXTRUST_TEST_MODE": { "type": "string", "enum": ["Y", "N"] } + }, + "required": ["ENABLE_OXTRUST_API_BOOLEAN", "ENABLE_OXTRUST_TEST_MODE"] + } + }, + "oxtrust-test-mode-enabled": { + "if": { + "properties": { + "ENABLE_OXTRUST_TEST_MODE": { "const": "Y" } + } + }, + "then": { + "properties": { + "ENABLE_OXTRUST_TEST_MODE_BOOLEAN": { "const": "true" } + }, + "required": ["ENABLE_OXTRUST_TEST_MODE_BOOLEAN"] + } + }, + "install-jackrabbit-yes": { + "if": { + "properties": { + "INSTALL_JACKRABBIT": { "const": "Y"} + } + }, + "then": { + "properties": { + "JACKRABBIT_STORAGE_SIZE": { "type": "string", "minLength": 3, "maxLength": 3}, + "JACKRABBIT_URL": { "type": "string", "minLength": 3, "format": "uri" }, + "JACKRABBIT_ADMIN_ID": { "type": "string", "minLength": 3 }, + "JACKRABBIT_ADMIN_PASSWORD": { "$ref": "#/definitions/password-pattern" }, + "JACKRABBIT_CLUSTER": { "type": "string", "enum": ["Y", "N"] } + }, + "required": [ + "JACKRABBIT_STORAGE_SIZE", + "JACKRABBIT_URL", + "JACKRABBIT_ADMIN_ID", + "JACKRABBIT_ADMIN_PASSWORD", + "JACKRABBIT_CLUSTER" + ] + } + }, + "install-jackrabbit-no": { + "if": { + "properties": { + "INSTALL_JACKRABBIT": { "const": "N"} + } + }, + "then": { + "properties": { + "JACKRABBIT_URL": { "type": "string", "minLength": 3, "format": "uri" }, + "JACKRABBIT_ADMIN_ID": { "$ref": "#/definitions/string-cannot-empty" }, + "JACKRABBIT_ADMIN_PASSWORD": { "$ref": "#/definitions/password-pattern" }, + "JACKRABBIT_CLUSTER": { "type": "string", "enum": ["Y", "N"] } + }, + "required": [ + "JACKRABBIT_URL", + "JACKRABBIT_ADMIN_ID", + "JACKRABBIT_ADMIN_PASSWORD", + "JACKRABBIT_CLUSTER" + ] + } + }, + "jackrabbit-cluster-enable": { + "if": { + "properties": { + "JACKRABBIT_CLUSTER": { "const": "Y"} + } + }, + "then": { + "properties": { + "POSTGRES_NAMESPACE": { "$ref": "#/definitions/string-cannot-empty" }, + "POSTGRES_REPLICAS": { "type":"number", "minimum": 1 }, + "POSTGRES_URL": { "$ref": "#/definitions/string-cannot-empty" }, + "JACKRABBIT_PG_USER": { "$ref": "#/definitions/string-cannot-empty" }, + "JACKRABBIT_PG_PASSWORD": { "$ref": "#/definitions/password-pattern" }, + "JACKRABBIT_DATABASE": { "$ref": "#/definitions/string-cannot-empty" } + }, + "required": [ + "POSTGRES_NAMESPACE", + "POSTGRES_REPLICAS", + "POSTGRES_URL", + "JACKRABBIT_PG_USER", + "JACKRABBIT_PG_PASSWORD", + "JACKRABBIT_DATABASE" + ] + + } + }, + "istio-ingress-yes": { + "if": { + "properties": { + "USE_ISTIO_INGRESS": { "const": "Y"} + } + }, + "then": { + "properties": { + "USE_ISTIO": { "const": "Y"}, + "LB_ADD": { "$ref": "#/definitions/string-cannot-empty" } + }, + "required": ["USE_ISTIO", "LB_ADD"] + } + }, + "istio-yes": { + "if": { + "properties": { + "USE_ISTIO": { "const": "Y" } + } + }, + "then": { + "properties": { + "ISTIO_SYSTEM_NAMESPACE": { "$ref": "#/definitions/string-cannot-empty" } + }, + "required": ["ISTIO_SYSTEM_NAMESPACE"] + } + }, + "test-environment": { + "if": { + "properties": { + "DEPLOYMENT_ARCH": { "enum": ["aks", "eks", "gke", "do", "local"] } + } + }, + "then": { + "properties": { + "TEST_ENVIRONMENT": { "type": "string", "enum": ["Y", "N"] }, + "NODE_SSH_KEY": { "$ref": "#/definitions/string-cannot-empty" } + }, + "required": ["TEST_ENVIRONMENT", "NODE_SSH_KEY"] + } + }, + "network-aws": { + "if": { + "properties": { + "DEPLOYMENT_ARCH": { "const": "eks" }, + "USE_ISTIO_INGRESS": { "const": "N" } + } + }, + "then": { + "properties": { + "AWS_LB_TYPE": { "type": "string", "enum": ["clb", "nlb", "alb"] }, + "USE_ARN": { "type": "string", "enum": ["Y", "N"] } + }, + "required": ["AWS_LB_TYPE", "USE_ARN"] + } + }, + "use-arn-yes": { + "if": { + "properties": { + "USE_ARN": { "const": "Y" } + } + }, + "then": { + "properties": { + "AWS_VPC_CIDR": { "$ref": "#/definitions/string-cannot-empty" }, + "ARN_AWS_IAM": { "$ref": "#/definitions/string-cannot-empty" } + }, + "required": ["AWS_VPC_CIDR", "ARN_AWS_IAM"] + } + }, + "deployment-arch-gke": { + "if": { + "properties": { + "DEPLOYMENT_ARCH": { "const": "gke" } + } + }, + "then": { + "properties": { + "GMAIL_ACCOUNT": { "type": "string", "format": "email", "minLength": 3 } + }, + "required": ["GOOGLE_NODE_HOME_DIR"] + } + }, + "persistence-backend-ldap": { + "if": { + "properties": { + "PERSISTENCE_BACKEND": { "const": "ldap"} + } + }, + "then": { + "properties": { + "ENABLED_SERVICES_LIST": { + "contains": { "enum" : ["ldap"] }, + "errors": { + "contains": "ldap key not found" + } + } + } + } + }, + "persistence-backend-hybrid": { + "if": { + "properties": { + "PERSISTENCE_BACKEND": { "const": "hybrid"} + } + }, + "then": { + "properties": { + "HYBRID_LDAP_HELD_DATA": { + "type": "string", + "enum" : ["default", "user", "site", "cache", "token", "session"] + } + } + } + }, + "microk8s-architecture": { + "if": { + "oneOf": [ + { + "properties": { + "DEPLOYMENT_ARCH": { "const": "microk8s" }, + "PERSISTENCE_BACKEND": { "enum": ["hybrid", "ldap"] }, + "APP_VOLUME_TYPE": { "not" : {"const": "" } } + } + }, + { + "properties": { + "DEPLOYMENT_ARCH": { "const": "microk8s" }, + "INSTALL_JACKRABBIT": { "const": "Y" }, + "APP_VOLUME_TYPE": { "not" : {"const": "" } } + } + } + ] + }, + "then": { + "properties": { + "APP_VOLUME_TYPE": { "const": 1 } + } + } + }, + "minikube-architecture": { + "if": { + "oneOf": [ + { + "properties": { + "DEPLOYMENT_ARCH": { "const": "minikube" }, + "PERSISTENCE_BACKEND": { "enum": ["hybrid", "ldap"] }, + "APP_VOLUME_TYPE": { "not" : {"const": "" } } + } + }, + { + "properties": { + "DEPLOYMENT_ARCH": { "const": "minikube" }, + "INSTALL_JACKRABBIT": { "const": "Y" }, + "APP_VOLUME_TYPE": { "not" : {"const": "" } } + } + } + ] + }, + "then": { + "properties": { + "APP_VOLUME_TYPE": { "const": 2 } + } + } + }, + "eks-architecture": { + "if": { + "oneOf": [ + { + "properties": { + "DEPLOYMENT_ARCH": { "const": "eks" }, + "PERSISTENCE_BACKEND": { "enum": ["hybrid", "ldap"] }, + "APP_VOLUME_TYPE": { "not" : {"const": "" } } + } + }, + { + "properties": { + "DEPLOYMENT_ARCH": { "const": "eks" }, + "INSTALL_JACKRABBIT": { "const": "Y" }, + "APP_VOLUME_TYPE": { "not" : {"const": "" } } + } + } + ] + }, + "then": { + "properties": { + "APP_VOLUME_TYPE": { "type": "number", "enum": [6, 7, 8] } + } + } + }, + "gke-architecture": { + "if": { + "oneOf": [ + { + "properties": { + "DEPLOYMENT_ARCH": { "const": "gke" }, + "PERSISTENCE_BACKEND": { "enum": ["hybrid", "ldap"] }, + "APP_VOLUME_TYPE": { "not" : {"const": "" } } + } + }, + { + "properties": { + "DEPLOYMENT_ARCH": { "const": "gke" }, + "INSTALL_JACKRABBIT": { "const": "Y" }, + "APP_VOLUME_TYPE": { "not" : {"const": "" } } + } + } + ] + }, + "then": { + "properties": { + "APP_VOLUME_TYPE": { "type": "number", "enum": [11, 12, 13] } + } + } + }, + "aks-architecture": { + "if": { + "oneOf": [ + { + "properties": { + "DEPLOYMENT_ARCH": { "const": "aks" }, + "PERSISTENCE_BACKEND": { "enum": ["hybrid", "ldap"] }, + "APP_VOLUME_TYPE": { "not" : {"const": "" } } + } + }, + { + "properties": { + "DEPLOYMENT_ARCH": { "const": "aks" }, + "INSTALL_JACKRABBIT": { "const": "Y" }, + "APP_VOLUME_TYPE": { "not" : {"const": "" } } + } + } + ] + }, + "then": { + "properties": { + "APP_VOLUME_TYPE": { "type": "number", "enum": [16, 17, 18] } + } + } + }, + "do-architecture": { + "if": { + "oneOf": [ + { + "properties": { + "DEPLOYMENT_ARCH": { "const": "do" }, + "PERSISTENCE_BACKEND": { "enum": ["hybrid", "ldap"] }, + "APP_VOLUME_TYPE": { "not" : {"const": "" } } + } + }, + { + "properties": { + "DEPLOYMENT_ARCH": { "const": "do" }, + "INSTALL_JACKRABBIT": { "const": "Y" }, + "APP_VOLUME_TYPE": { "not" : {"const": "" } } + } + } + ] + }, + "then": { + "properties": { + "APP_VOLUME_TYPE": { "type": "number", "enum": [21, 22, 23] } + } + } + }, + "local-architecture": { + "if": { + "oneOf": [ + { + "properties": { + "DEPLOYMENT_ARCH": { "const": "local" }, + "PERSISTENCE_BACKEND": { "enum": ["hybrid", "ldap"] }, + "APP_VOLUME_TYPE": { "not" : {"const": "" } } + } + }, + { + "properties": { + "DEPLOYMENT_ARCH": { "const": "local" }, + "INSTALL_JACKRABBIT": { "const": "Y" }, + "APP_VOLUME_TYPE": { "not" : {"const": "" } } + } + } + ] + }, + "then": { + "properties": { + "APP_VOLUME_TYPE": { "type": "number", "enum": [26] } + } + } + }, + "ldap-volume-identifier": { + "if": { + "properties": { + "APP_VOLUME_TYPE": { "type": "number", "enum": [8, 13] }, + "PERSISTENCE_BACKEND": { "type": "string", "enum": ["hybrid", "ldap"]}, + "INSTALL_JACKRABBIT": { "const": "Y" } + } + }, + "then": { + "properties": { + "LDAP_STATIC_VOLUME_ID": { "type": "string", "minLength": 1 } + }, + "required": ["LDAP_STATIC_VOLUME_ID"] + } + }, + "ldap-disk-uris": { + "if": { + "properties": { + "APP_VOLUME_TYPE": { "const": 18 }, + "PERSISTENCE_BACKEND": { "type": "string", "enum": ["hybrid", "ldap"]}, + "INSTALL_JACKRABBIT": { "const": "Y" } + } + }, + "then": { + "properties": { + "LDAP_STATIC_DISK_URI": { "type": "string", "minLength": 1 } + }, + "required": ["LDAP_STATIC_DISK_URI"] + } + }, + "ldap-jackrabbit-volume-on-aks": { + "if": { + "properties": { + "DEPLOYMENT_ARCH": { "const": "aks" }, + "PERSISTENCE_BACKEND": { "enum": ["hybrid", "ldap"]}, + "INSTALL_JACKRABBIT": { "const": "Y" } + } + }, + "then": { + "properties": { + "LDAP_JACKRABBIT_VOLUME": { + "type": "string", + "enum": ["Standard_LRS", "Premium_LRS", "StandardSSD_LRS", "UltraSSD_LRS"] } + }, + "required": ["LDAP_JACKRABBIT_VOLUME"] + } + }, + "ldap-jackrabbit-volume-on-eks": { + "if": { + "properties": { + "DEPLOYMENT_ARCH": { "const": "eks" }, + "PERSISTENCE_BACKEND": { "enum": ["hybrid", "ldap"]}, + "INSTALL_JACKRABBIT": { "const": "Y" } + } + }, + "then": { + "properties": { + "LDAP_JACKRABBIT_VOLUME": { + "type": "string", + "enum": ["gp2", "io1", "st1", "sc1"] } + }, + "required": ["LDAP_JACKRABBIT_VOLUME"] + } + }, + "ldap-jackrabbit-volume-on-gke": { + "if": { + "properties": { + "DEPLOYMENT_ARCH": { "const": "gke" }, + "PERSISTENCE_BACKEND": { "enum": ["hybrid", "ldap"]}, + "INSTALL_JACKRABBIT": { "const": "Y" } + } + }, + "then": { + "properties": { + "LDAP_JACKRABBIT_VOLUME": { + "type": "string", + "enum": ["pd-standard", "pd-ssd"] } + }, + "required": ["LDAP_JACKRABBIT_VOLUME"] + } + }, + "ldap-storage": { + "if": { + "properties": { + "PERSISTENCE_BACKEND": { "enum": ["hybrid", "ldap"]} + } + }, + "then": { + "properties": { + "LDAP_STORAGE_SIZE": { "$ref": "#/definitions/string-cannot-empty" } + }, + "required": ["LDAP_STORAGE_SIZE"] + } + }, + "couchbase-multi-cluster": { + "if": { + "properties": { + "PERSISTENCE_BACKEND": { "enum": ["hybrid", "couchbase"]}, + "DEPLOYMENT_ARCH": { "enum": ["aks", "eks", "gke", "do", "local"]} + } + }, + "then": { + "properties": { + "DEPLOY_MULTI_CLUSTER": { + "type": "string", + "enum": ["Y", "N"] + } + }, + "required": ["DEPLOY_MULTI_CLUSTER"] + } + }, + "couchbase-persistence-backend": { + "if": { + "properties": { + "PERSISTENCE_BACKEND": { "enum": ["hybrid", "couchbase"]} + } + }, + "then": { + "properties": { + "DEPLOYMENT_ARCH": { + "type": "string", + "enum": ["microk8s", "minikube", "aks", "eks", "gke", "do", "local"] + }, + "CN_NAMESPACE": { "$ref": "#/definitions/string-cannot-empty" }, + "HOST_EXT_IP": { "type": "string", "format": "ipv4", "minLength": 7 }, + "INSTALL_COUCHBASE": { "type": "string", "enum": ["Y", "N"] } + }, + "required": ["INSTALL_COUCHBASE"] + } + }, + "install-couchbase": { + "properties": { + "COUCHBASE_CLUSTER_FILE_OVERRIDE": {"type": "string", "enum": ["Y", "N"] }, + "COUCHBASE_USE_LOW_RESOURCES": {"type": "string", "enum": ["Y", "N"] }, + "COUCHBASE_NAMESPACE": { "$ref": "#/definitions/string-cannot-empty" }, + "COUCHBASE_CLUSTER_NAME": { "$ref": "#/definitions/string-cannot-empty" }, + "COUCHBASE_URL": { "$ref": "#/definitions/string-cannot-empty" }, + "COUCHBASE_INDEX_NUM_REPLICA": { "$ref": "#/definitions/string-cannot-empty" }, + "COUCHBASE_SUPERUSER": { "$ref": "#/definitions/string-cannot-empty" }, + "COUCHBASE_SUPERUSER_PASSWORD": { "$ref": "#/definitions/password-pattern" }, + "COUCHBASE_USER": { "$ref": "#/definitions/string-cannot-empty" }, + "COUCHBASE_BUCKET_PREFIX": { "$ref": "#/definitions/string-cannot-empty" }, + "COUCHBASE_PASSWORD": { "$ref": "#/definitions/password-pattern" } + }, + "required": [ + "COUCHBASE_CLUSTER_FILE_OVERRIDE", + "COUCHBASE_USE_LOW_RESOURCES", + "COUCHBASE_NAMESPACE", + "COUCHBASE_CLUSTER_NAME", + "COUCHBASE_URL", + "COUCHBASE_INDEX_NUM_REPLICA", + "COUCHBASE_SUPERUSER", + "COUCHBASE_SUPERUSER_PASSWORD", + "COUCHBASE_USER", + "COUCHBASE_BUCKET_PREFIX", + "COUCHBASE_PASSWORD" + ] + }, + "install-couchbase-yes": { + "if": { + "properties": { + "INSTALL_COUCHBASE": { "const": "Y" } + } + }, + "then": { + "$ref": "#/definitions/install-couchbase" + } + }, + "install-couchbase-no": { + "if": { + "properties": { + "INSTALL_COUCHBASE": { "const": "N" } + } + }, + "then": { + "properties": { + "COUCHBASE_CRT": { "$ref": "#/definitions/string-cannot-empty" } + }, + "$ref": "#/definitions/install-couchbase", + "required": ["COUCHBASE_CRT"] + } + }, + "couchbase-not-use-low-resource": { + "if": { + "properties": { + "COUCHBASE_USE_LOW_RESOURCES": { "const": "N" }, + "COUCHBASE_CLUSTER_FILE_OVERRIDE": { "const": "N" }, + "INSTALL_COUCHBASE": { "const": "Y" } + } + }, + "then": { + "properties": { + "NUMBER_OF_EXPECTED_USERS": { "type": "number", "minimum": 1 }, + "USING_RESOURCE_OWNER_PASSWORD_CRED_GRANT_FLOW": { "type": "string", "enum": ["Y", "N"] }, + "USING_CODE_FLOW": { "type": "string", "enum": ["Y", "N"] }, + "USING_SCIM_FLOW": { "type": "string", "enum": ["Y", "N"] }, + "COUCHBASE_DATA_NODES": { "$ref": "#/definitions/emptiable-number" }, + "COUCHBASE_INDEX_NODES": { "$ref": "#/definitions/emptiable-number" }, + "COUCHBASE_QUERY_NODES": { "$ref": "#/definitions/emptiable-number" }, + "COUCHBASE_SEARCH_EVENTING_ANALYTICS_NODES": { "$ref": "#/definitions/emptiable-number" }, + "COUCHBASE_GENERAL_STORAGE": { "$ref": "#/definitions/emptiable-number" }, + "COUCHBASE_INDEX_STORAGE": { "$ref": "#/definitions/emptiable-number" }, + "COUCHBASE_QUERY_STORAGE": { "$ref": "#/definitions/emptiable-number" }, + "COUCHBASE_ANALYTICS_STORAGE": { "$ref": "#/definitions/emptiable-number" }, + "COUCHBASE_VOLUME_TYPE": { + "type": "string", + "enum": [ + "pd-standard", + "pd-ssd", + "gp2", + "io1", + "st1", + "sc1", + "Standard_LRS", + "Premium_LRS", + "StandardSSD_LRS", + "UltraSSD_LRS" + ] + } + }, + "required": [ + "NUMBER_OF_EXPECTED_USERS", + "USING_RESOURCE_OWNER_PASSWORD_CRED_GRANT_FLOW", + "USING_CODE_FLOW", + "USING_SCIM_FLOW", + "COUCHBASE_DATA_NODES", + "COUCHBASE_INDEX_NODES", + "COUCHBASE_QUERY_NODES", + "COUCHBASE_SEARCH_EVENTING_ANALYTICS_NODES", + "COUCHBASE_GENERAL_STORAGE", + "COUCHBASE_INDEX_STORAGE", + "COUCHBASE_QUERY_STORAGE", + "COUCHBASE_ANALYTICS_STORAGE", + "COUCHBASE_VOLUME_TYPE" + ] + } + }, + "cache-type-redis": { + "if": { + "properties": { + "CN_CACHE_TYPE": { "const": "REDIS" } + } + }, + "then": { + "properties": { + "REDIS_TYPE": { "type": "string", "enum": ["STANDALONE", "CLUSTER"] }, + "INSTALL_REDIS": { "type": "string", "enum": ["Y", "N"] }, + "REDIS_URL": { "$ref": "#/definitions/string-cannot-empty" } + } + }, + "required": ["REDIS_TYPE", "INSTALL_REDIS", "REDIS_URL"] + }, + "install-redis-yes": { + "if": { + "properties": { + "INSTALL_REDIS": { "const": "Y" } + } + }, + "then": { + "properties": { + "REDIS_MASTER_NODES": { "type": "number", "minimum": 3 }, + "REDIS_NODES_PER_MASTER": { "type": "number", "minimum": 1 }, + "REDIS_NAMESPACE": { "type": "string", "minimum": 1 } + } + }, + "required": ["REDIS_MASTER_NODES", "REDIS_NODES_PER_MASTER", "REDIS_NAMESPACE"] + }, + "install-redis-no": { + "if": { + "properties": { + "INSTALL_REDIS": { "const": "N" } + } + }, + "then": { + "properties": { + "REDIS_PW": { "type": "string", "minLength": 6 } + } + }, + "required": ["REDIS_PW"] + }, + "backup-hybrid-couchbase": { + "if": { + "properties": { + "DEPLOYMENT_ARCH": { "enum": ["aks", "eks", "gke", "do", "local"] }, + "PERSISTENCE_BACKEND": { "enum": ["hybrid", "couchbase"] } + } + }, + "then": { + "properties": { + "COUCHBASE_INCR_BACKUP_SCHEDULE": { "$ref": "#/definitions/string-cannot-empty" }, + "COUCHBASE_FULL_BACKUP_SCHEDULE": { "$ref": "#/definitions/string-cannot-empty" }, + "COUCHBASE_BACKUP_RETENTION_TIME": { "$ref": "#/definitions/string-cannot-empty" }, + "COUCHBASE_BACKUP_STORAGE_SIZE": { "$ref": "#/definitions/string-cannot-empty" } + } + }, + "required": [ + "COUCHBASE_INCR_BACKUP_SCHEDULE", + "COUCHBASE_FULL_BACKUP_SCHEDULE", + "COUCHBASE_BACKUP_RETENTION_TIME", + "COUCHBASE_BACKUP_STORAGE_SIZE" + ] + }, + "backup-ldap": { + "if": { + "properties": { + "DEPLOYMENT_ARCH": { "enum": ["aks", "eks", "gke", "do", "local"] }, + "PERSISTENCE_BACKEND": { "const": "ldap" } + } + }, + "then": { + "properties": { + "LDAP_BACKUP_SCHEDULE": { "$ref": "#/definitions/string-cannot-empty" } + } + }, + "required": [ + "LDAP_BACKUP_SCHEDULE" + ] + } + } +} \ No newline at end of file diff --git a/helm/setup.py b/helm/setup.py new file mode 100755 index 00000000000..988d8b05259 --- /dev/null +++ b/helm/setup.py @@ -0,0 +1,66 @@ +""" + License terms and conditions for Gluu Cloud Native Edition: + https://www.apache.org/licenses/LICENSE-2.0 +""" + +import codecs +import os +import re +from setuptools import setup +from setuptools import find_packages + + +def find_version(*file_paths): + here = os.path.abspath(os.path.dirname(__file__)) + with codecs.open(os.path.join(here, *file_paths), 'r') as f: + version_file = f.read() + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", + version_file, re.M) + if version_match: + return version_match.group(1) + raise RuntimeError("Unable to find version string.") + + +setup( + name="pygluu-kubernetes", + version=find_version("pygluu", "kubernetes", "__init__.py"), + url="https://gluu.org", + copyright="Copyright 2020, Gluu Cloud Native Edition", + license="Apache 2.0 ", + author="Gluu", + author_email="mo@gluu.org", + maintainer="Mohammad Abudayyeh", + status="Dev", + description="", + long_description=__doc__, + packages=find_packages(), + zip_safe=False, + install_requires=[ + "ruamel.yaml>=0.16.5", + "pyOpenSSL>=19.1.0", + "cryptography>=2.8", + "kubernetes==18.20.0", + "Click!=7.0,>=6.7", + "email_validator >= 1.1.0", + "Flask-SocketIO >= 4.3.1", + "Pygtail >= 0.11.1", + "gevent >= 20.9.0", + "jsonschema >= 3.2.0", + "dotty-dict >= 1.3.0", + "PyYAML >= 5.4.1" + ], + classifiers=[ + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache 2.0 License", + "Topic :: Software Development :: Libraries :: Python Modules", + "Programming Language :: Python", + "Programming Language :: Python :: 3s", + "Programming Language :: Python :: 3.6", + ], + include_package_data=True, + entry_points={ + "console_scripts": [ + "pygluu-kubernetes=pygluu.kubernetes.create:main", + ], + }, +) diff --git a/helm/tests/conftest.py b/helm/tests/conftest.py new file mode 100644 index 00000000000..eb5a3b73c50 --- /dev/null +++ b/helm/tests/conftest.py @@ -0,0 +1,10 @@ +import pytest + + +@pytest.fixture() +def settings(): + from pygluu.kubernetes.settings import ValuesHandler, unlink_values_yaml + + handler = ValuesHandler() + yield handler + unlink_values_yaml() diff --git a/helm/tests/terminal/test_architecture.py b/helm/tests/terminal/test_architecture.py new file mode 100644 index 00000000000..b1005d45de5 --- /dev/null +++ b/helm/tests/terminal/test_architecture.py @@ -0,0 +1,22 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + (1, "microk8s.io/hostpath"), + (2, "k8s.io/minikube-hostpath"), + (3, "kubernetes.io/aws-ebs"), + (4, "kubernetes.io/gce-pd"), + (5, "kubernetes.io/azure-disk"), + (6, "dobs.csi.digitalocean.com"), + (7, "openebs.io/local"), + ("random", "microk8s.io/hostpath"), +]) +def test_arch(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.architecture import PromptArch + + monkeypatch.setattr("click.prompt", lambda x, default: given) + + settings.set("global.storageClass.provisioner", "") + prompt = PromptArch(settings) + prompt.prompt_arch() + assert settings.get("global.storageClass.provisioner") == expected diff --git a/helm/tests/terminal/test_aws.py b/helm/tests/terminal/test_aws.py new file mode 100644 index 00000000000..7fd2176d56a --- /dev/null +++ b/helm/tests/terminal/test_aws.py @@ -0,0 +1,28 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + ("", "clb"), # default + (1, "clb"), + (2, "nlb"), + (3, "alb"), +]) +def test_aws_loadbalancer(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.aws import PromptAws + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("installer-settings.aws.arn.enabled", False) + PromptAws(settings).prompt_aws_lb() + assert settings.get("installer-settings.aws.lbType") == expected + + +def test_aws_vpccidr(monkeypatch, settings): + from pygluu.kubernetes.terminal.aws import PromptAws + + monkeypatch.setattr("click.prompt", lambda x, default: "0.0.0.0/0") + + settings.set("installer-settings.aws.arn.enabled", True) + settings.set("installer-settings.aws.vpcCidr", "") + PromptAws(settings).prompt_aws_lb() + assert settings.get("installer-settings.aws.vpcCidr") == "0.0.0.0/0" diff --git a/helm/tests/terminal/test_backup.py b/helm/tests/terminal/test_backup.py new file mode 100644 index 00000000000..34953c120eb --- /dev/null +++ b/helm/tests/terminal/test_backup.py @@ -0,0 +1,115 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + ("", "*/30 * * * *"), # default + ("*/10 * * * *", "*/10 * * * *"), +]) +def test_backup_ldap(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.backup import PromptBackup + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("global.cnPersistenceType", "ldap") + settings.set("installer-settings.ldap.backup.fullSchedule", "") + + PromptBackup(settings).prompt_backup() + assert settings.get("installer-settings.ldap.backup.fullSchedule") == expected + + +@pytest.mark.parametrize("given, expected, type_", [ + ("", "*/30 * * * *", "couchbase"), # default + ("*/10 * * * *", "*/10 * * * *", "couchbase"), + ("", "*/30 * * * *", "hybrid"), # default + ("*/10 * * * *", "*/10 * * * *", "hybrid"), +]) +def test_backup_not_ldap_incr(monkeypatch, settings, given, expected, type_): + from pygluu.kubernetes.terminal.backup import PromptBackup + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("global.cnPersistenceType", type_) + settings.set("installer-settings.couchbase.backup.fullSchedule", "0 2 * * 6") + settings.set("installer-settings.couchbase.backup.retentionTime", "168h") + settings.set("installer-settings.couchbase.backup.storageSize", "20Gi") + settings.set("installer-settings.couchbase.backup.incrementalSchedule", "") + + PromptBackup(settings).prompt_backup() + assert settings.get("installer-settings.couchbase.backup.incrementalSchedule") == expected + + +@pytest.mark.parametrize("given, expected, type_", [ + ("", "0 2 * * 6", "couchbase"), # default + ("0 1 * * 6", "0 1 * * 6", "couchbase"), + ("", "0 2 * * 6", "hybrid"), # default + ("0 1 * * 6", "0 1 * * 6", "hybrid"), +]) +def test_backup_not_ldap_full(monkeypatch, settings, given, expected, type_): + from pygluu.kubernetes.terminal.backup import PromptBackup + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("global.cnPersistenceType", type_) + settings.set("installer-settings.couchbase.backup.incrementalSchedule", "*/30 * * * *") + settings.set("installer-settings.couchbase.backup.retentionTime", "168h") + settings.set("installer-settings.couchbase.backup.storageSize", "20Gi") + settings.set("installer-settings.couchbase.backup.incrementalSchedule", "") + + PromptBackup(settings).prompt_backup() + assert settings.get("installer-settings.couchbase.backup.incrementalSchedule") == expected + + +@pytest.mark.parametrize("given, expected, type_", [ + ("", "168h", "couchbase"), # default + ("160h", "160h", "couchbase"), + ("", "168h", "hybrid"), # default + ("160h", "160h", "hybrid"), +]) +def test_backup_not_ldap_retention(monkeypatch, settings, given, expected, type_): + from pygluu.kubernetes.terminal.backup import PromptBackup + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("global.cnPersistenceType", type_) + settings.set("installer-settings.couchbase.backup.incrementalSchedule", "*/30 * * * *") + settings.set("installer-settings.couchbase.backup.fullSchedule", "0 2 * * 6") + settings.set("installer-settings.couchbase.backup.storageSize", "20Gi") + settings.set("installer-settings.couchbase.backup.retentionTime", "") + + PromptBackup(settings).prompt_backup() + assert settings.get("installer-settings.couchbase.backup.retentionTime") == expected + + +@pytest.mark.parametrize("given, expected, type_", [ + ("", "20Gi", "couchbase"), # default + ("10Gi", "10Gi", "couchbase"), + ("", "20Gi", "hybrid"), # default + ("10Gi", "10Gi", "hybrid"), +]) +def test_backup_not_ldap_storage(monkeypatch, settings, given, expected, type_): + from pygluu.kubernetes.terminal.backup import PromptBackup + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("global.cnPersistenceType", type_) + settings.set("installer-settings.couchbase.backup.incrementalSchedule", "*/30 * * * *") + settings.set("installer-settings.couchbase.backup.fullSchedule", "0 2 * * 6") + settings.set("installer-settings.couchbase.backup.retentionTime", "168h") + settings.set("installer-settings.couchbase.backup.storageSize", "") + + PromptBackup(settings).prompt_backup() + assert settings.get("installer-settings.couchbase.backup.storageSize") == expected + + +def test_backup_fullschedule(monkeypatch, settings): + from pygluu.kubernetes.terminal.backup import PromptBackup + + + monkeypatch.setattr("click.prompt", lambda x, default: "0 2 * * 6") + + settings.set("global.cnPersistenceType", "couchbase") + settings.set("installer-settings.couchbase.backup.fullSchedule", "") + + PromptBackup(settings).prompt_backup() + + assert settings.get("installer-settings.couchbase.backup.fullSchedule") == "0 2 * * 6" \ No newline at end of file diff --git a/helm/tests/terminal/test_cache.py b/helm/tests/terminal/test_cache.py new file mode 100644 index 00000000000..8e21b0af904 --- /dev/null +++ b/helm/tests/terminal/test_cache.py @@ -0,0 +1,19 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + ("", "NATIVE_PERSISTENCE"), # default + (1, "NATIVE_PERSISTENCE"), + (2, "IN_MEMORY"), + (3, "REDIS"), +]) +def test_cache_type(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.cache import PromptCache + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + # mock PromptRedis as we will have separate testcases for it + monkeypatch.setattr("pygluu.kubernetes.terminal.redis.PromptRedis.prompt_redis", lambda x: None) + settings.set("config.configmap.cnCacheType", "") + + PromptCache(settings).prompt_cache_type() + assert settings.get("config.configmap.cnCacheType") == expected diff --git a/helm/tests/terminal/test_configuration.py b/helm/tests/terminal/test_configuration.py new file mode 100644 index 00000000000..49ac47ece4f --- /dev/null +++ b/helm/tests/terminal/test_configuration.py @@ -0,0 +1,223 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + ("", "US"), # default + ("random", "random"), +]) +def test_config_country(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.configuration import PromptConfiguration + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("config.countryCode", "") + settings.set("config.state", "TX") + settings.set("config.city", "Austin") + settings.set("config.email", "support@gluu.org") + settings.set("config.orgName", "Gluu") + settings.set("config.config.adminPassword", "Admin GUI") + settings.set("global.fqdn", "demoexample.gluu.org") + settings.set("config.migration.enabled", False) + + prompt = PromptConfiguration(settings) + prompt.prompt_config() + + assert settings.get("config.countryCode") == expected + + +@pytest.mark.parametrize("given, expected", [ + ("", "TX"), # default + ("random", "random"), +]) +def test_config_state(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.configuration import PromptConfiguration + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("config.countryCode", "US") + settings.set("config.state", "") + settings.set("config.city", "Austin") + settings.set("config.email", "support@gluu.org") + settings.set("config.orgName", "Gluu") + settings.set("config.config.adminPassword", "Admin GUI") + settings.set("global.fqdn", "demoexample.gluu.org") + settings.set("config.migration.enabled", False) + + prompt = PromptConfiguration(settings) + prompt.prompt_config() + + assert settings.get("config.state") == expected + + +@pytest.mark.parametrize("given, expected", [ + ("", "Austin"), # default + ("random", "random"), +]) +def test_config_city(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.configuration import PromptConfiguration + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("config.countryCode", "US") + settings.set("config.state", "TX") + settings.set("config.city", "") + settings.set("config.email", "support@gluu.org") + settings.set("config.orgName", "Gluu") + settings.set("config.config.adminPassword", "Admin GUI") + settings.set("global.fqdn", "demoexample.gluu.org") + settings.set("config.migration.enabled", False) + + prompt = PromptConfiguration(settings) + prompt.prompt_config() + + assert settings.get("config.city") == expected + + +@pytest.mark.parametrize("given, expected", [ + ("", "support@gluu.org"), # default + ("random", "random"), +]) +def test_config_email(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.configuration import PromptConfiguration + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("config.countryCode", "US") + settings.set("config.state", "TX") + settings.set("config.city", "Austin") + settings.set("config.email", "") + settings.set("config.orgName", "Gluu") + settings.set("config.config.adminPassword", "Admin GUI") + settings.set("global.fqdn", "demoexample.gluu.org") + settings.set("config.migration.enabled", False) + + prompt = PromptConfiguration(settings) + prompt.prompt_config() + + assert settings.get("config.email") == expected + + +@pytest.mark.parametrize("given, expected", [ + ("", "Gluu"), # default + ("random", "random"), +]) +def test_config_org(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.configuration import PromptConfiguration + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("config.countryCode", "US") + settings.set("config.state", "TX") + settings.set("config.city", "Austin") + settings.set("config.email", "support@gluu.org") + settings.set("config.orgName", "") + settings.set("config.config.adminPassword", "Admin GUI") + settings.set("global.fqdn", "demoexample.gluu.org") + settings.set("config.migration.enabled", False) + + prompt = PromptConfiguration(settings) + prompt.prompt_config() + + assert settings.get("config.orgName") == expected + + +@pytest.mark.parametrize("given, expected", [ + ("", "demoexample.gluu.org"), # default +]) +def test_config_hostname(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.configuration import PromptConfiguration + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("config.countryCode", "US") + settings.set("config.state", "TX") + settings.set("config.city", "Austin") + settings.set("config.email", "support@gluu.org") + settings.set("config.orgName", "Gluu") + settings.set("config.adminPassword", "Admin GUI") + settings.set("global.fqdn", "") + settings.set("config.migration.enabled", False) + + prompt = PromptConfiguration(settings) + prompt.prompt_config() + + assert settings.get("global.fqdn") == expected + + +@pytest.mark.parametrize("given, expected", [ + ("", "demoexample.gluu.org"), # default +]) +def test_config_hostname_2(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.configuration import PromptConfiguration + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("config.countryCode", "US") + settings.set("config.state", "TX") + settings.set("config.city", "Austin") + settings.set("config.email", "support@gluu.org") + settings.set("config.orgName", "Gluu") + settings.set("config.adminPassword", "Admin GUI") + settings.set("global.fqdn", "") + settings.set("config.migration.enabled", False) + + prompt = PromptConfiguration(settings) + prompt.prompt_config() + + assert settings.get("global.fqdn") == expected + + +@pytest.mark.parametrize("given, expected", [ + ("", "./ce-migration"), + ("migration", "migration") + +]) +def test_config_migration_dir(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.configuration import PromptConfiguration + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("config.countryCode", "US") + settings.set("config.state", "TX") + settings.set("config.city", "Austin") + settings.set("config.email", "support@gluu.org") + settings.set("config.orgName", "Gluu") + settings.set("config.adminPassword", "Admin GUI") + settings.set("global.fqdn", "demoexample.gluu.org") + settings.set("config.migration.enabled", True) + settings.set("config.migration.migrationDir", "") + + prompt = PromptConfiguration(settings) + prompt.prompt_config() + + assert settings.get("config.migration.migrationDir") == expected + + +@pytest.mark.parametrize("given, expected", [ + ("", "ldif"), + ("ldif", "ldif"), + ("couchbase+json", "couchbase+json"), + ("spanner+avro", "spanner+avro"), + ("postgresql+json", "postgresql+json"), + ("mysql+json", "mysql+json") +]) +def test_config_migration_dir(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.configuration import PromptConfiguration + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("config.countryCode", "US") + settings.set("config.state", "TX") + settings.set("config.city", "Austin") + settings.set("config.email", "support@gluu.org") + settings.set("config.orgName", "Gluu") + settings.set("config.adminPassword", "Admin GUI") + settings.set("global.fqdn", "demoexample.gluu.org") + settings.set("config.migration.enabled", True) + settings.set("config.migration.migrationDir", "./ce-migration") + settings.set("config.migration.migrationDataFormat", "") + + prompt = PromptConfiguration(settings) + prompt.prompt_config() + + assert settings.get("config.migration.migrationDataFormat") == expected \ No newline at end of file diff --git a/helm/tests/terminal/test_confirmsettings.py b/helm/tests/terminal/test_confirmsettings.py new file mode 100644 index 00000000000..b10d0089674 --- /dev/null +++ b/helm/tests/terminal/test_confirmsettings.py @@ -0,0 +1,21 @@ +def test_confirmsettings_confirm_params_accepted(monkeypatch, settings): + from pygluu.kubernetes.terminal.confirmsettings import PromptConfirmSettings + + monkeypatch.setattr("click.confirm", lambda x: True) + + settings.set("installer-settings.confirmSettings", "") + prompt = PromptConfirmSettings(settings) + prompt.confirm_params() + assert settings.get("installer-settings.confirmSettings") + + +def test_confirmsettings_confirm_params_rejected(monkeypatch, settings): + from pygluu.kubernetes.terminal.confirmsettings import PromptConfirmSettings + + monkeypatch.setattr("click.confirm", lambda x: False) + # mock Prompt.prompt + monkeypatch.setattr("pygluu.kubernetes.terminal.prompt.Prompt.prompt", lambda x: None) + + prompt = PromptConfirmSettings(settings) + prompt.confirm_params() + assert settings.get("installer-settings.confirmSettings") == False diff --git a/helm/tests/terminal/test_couchbase.py b/helm/tests/terminal/test_couchbase.py new file mode 100644 index 00000000000..297f120a57f --- /dev/null +++ b/helm/tests/terminal/test_couchbase.py @@ -0,0 +1,122 @@ +import pytest + +def test_prompt_couchbase_ip(monkeypatch, settings): + from pygluu.kubernetes.terminal.couchbase import PromptCouchbase + from pygluu.kubernetes.terminal.helpers import gather_ip + + monkeypatch.setattr("click.prompt", lambda x, default: gather_ip) + + settings.set("global.lbIp", "") + prompt = PromptCouchbase(settings) + prompt.prompt_couchbase() + assert settings.get("global.lbIp") == gather_ip + + +def test_prompt_couchbase_namespace(monkeypatch, settings): + from pygluu.kubernetes.terminal.couchbase import PromptCouchbase + + monkeypatch.setattr("click.prompt", lambda x, default: "cbns") + + settings.set("installer-settings.couchbase.namespace", "") + prompt = PromptCouchbase(settings) + prompt.prompt_couchbase() + assert settings.get("installer-settings.couchbase.namespace") == "cbns" + + +def test_prompt_couchbase_cluster(monkeypatch, settings): + from pygluu.kubernetes.terminal.couchbase import PromptCouchbase + + monkeypatch.setattr("click.prompt", lambda x, default: "cbgluu") + + settings.set("installer-settings.couchbase.clusterName", "") + prompt = PromptCouchbase(settings) + prompt.prompt_couchbase() + assert settings.get("installer-settings.couchbase.clusterName") == "cbgluu" + + +def test_prompt_couchbase_bucket(monkeypatch, settings): + from pygluu.kubernetes.terminal.couchbase import PromptCouchbase + + monkeypatch.setattr("click.prompt", lambda x, default: "gluu") + + settings.set("config.configmap.cnCouchbaseBucketPrefix", "") + prompt = PromptCouchbase(settings) + prompt.prompt_couchbase() + assert settings.get("config.configmap.cnCouchbaseBucketPrefix") == "gluu" + + +def test_prompt_couchbase_replicanum(monkeypatch, settings): + from pygluu.kubernetes.terminal.couchbase import PromptCouchbase + + monkeypatch.setattr("click.prompt", lambda x, default: "0") + + settings.set("config.configmap.cnCouchbaseIndexNumReplica", "") + prompt = PromptCouchbase(settings) + prompt.prompt_couchbase() + assert settings.get("config.configmap.cnCouchbaseIndexNumReplica") == "0" + + +def test_prompt_couchbase_superuser(monkeypatch, settings): + from pygluu.kubernetes.terminal.couchbase import PromptCouchbase + + monkeypatch.setattr("click.prompt", lambda x, default: "admin") + + settings.set("config.configmap.cnCouchbaseSuperUser", "") + prompt = PromptCouchbase(settings) + prompt.prompt_couchbase() + assert settings.get("config.configmap.cnCouchbaseSuperUser") == "admin" + + +def test_prompt_couchbase_user(monkeypatch, settings): + from pygluu.kubernetes.terminal.couchbase import PromptCouchbase + + monkeypatch.setattr("click.prompt", lambda x, default: "gluu") + + settings.set("config.configmap.cnCouchbaseUser", "") + prompt = PromptCouchbase(settings) + prompt.prompt_couchbase() + assert settings.get("config.configmap.cnCouchbaseUser") == "gluu" + + +def test_prompt_couchbase_users(monkeypatch, settings): + from pygluu.kubernetes.terminal.couchbase import PromptCouchbase + + monkeypatch.setattr("click.prompt", lambda x, default: "1000000") + + settings.set("installer-settings.couchbase.totalNumberOfExpectedUsers", "") + prompt = PromptCouchbase(settings) + prompt.prompt_couchbase_yaml() + assert settings.get("installer-settings.couchbase.totalNumberOfExpectedUsers") == "1000000" + + +def test_prompt_couchbase_transactions(monkeypatch, settings): + from pygluu.kubernetes.terminal.couchbase import PromptCouchbase + + monkeypatch.setattr("click.prompt", lambda x, default: 2000) + + settings.set("installer-settings.couchbase.totalNumberOfExpectedTransactionsPerSec", "") + prompt = PromptCouchbase(settings) + prompt.prompt_couchbase_yaml() + assert settings.get("installer-settings.couchbase.totalNumberOfExpectedTransactionsPerSec") == 2000 + + +def test_prompt_couchbase_volumetype(monkeypatch, settings): + from pygluu.kubernetes.terminal.couchbase import PromptCouchbase + + monkeypatch.setattr("click.prompt", lambda x, default: "io1") + + settings.set("installer-settings.couchbase.volumeType", "") + prompt = PromptCouchbase(settings) + prompt.prompt_couchbase_yaml() + assert settings.get("installer-settings.couchbase.volumeType") == "io1" + + +def test_prompt_couchbase_commonname(monkeypatch, settings): + from pygluu.kubernetes.terminal.couchbase import PromptCouchbase + + monkeypatch.setattr("click.prompt", lambda x, default: "Couchbase CA") + cm = "Couchbase CA" + settings.set("installer-settings.couchbase.commonName", cm) + prompt = PromptCouchbase(settings) + prompt.prompt_couchbase_yaml() + assert settings.get("installer-settings.couchbase.commonName") == cm \ No newline at end of file diff --git a/helm/tests/terminal/test_distribution.py b/helm/tests/terminal/test_distribution.py new file mode 100644 index 00000000000..04b8b5ad97e --- /dev/null +++ b/helm/tests/terminal/test_distribution.py @@ -0,0 +1,16 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + ("", "default"), # default + (1, "default"), + (2, "openbanking"), +]) +def test_distribution(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.distribution import PromptDistribution + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("global.distribution", "") + PromptDistribution(settings).prompt_distribution() + assert settings.get("global.distribution") == expected diff --git a/helm/tests/terminal/test_gke.py b/helm/tests/terminal/test_gke.py new file mode 100644 index 00000000000..97cf2cff47c --- /dev/null +++ b/helm/tests/terminal/test_gke.py @@ -0,0 +1,30 @@ +def test_prompt_gke_account(monkeypatch, settings): + from pygluu.kubernetes.terminal.gke import PromptGke + + monkeypatch.setattr("click.prompt", lambda x: "random@gmail.local") + + PromptGke(settings).prompt_gke() + assert settings.get("GMAIL_ACCOUNT") == "random@gmail.local" + + +def test_prompt_gke_vol_type(monkeypatch, settings): + from pygluu.kubernetes.terminal.gke import PromptGke + + class FakePopen: + returncode = 0 + + def __init__(self, *args, **kwargs): + pass + + def communicate(self): + return b"/home/random", b"" + + monkeypatch.setattr("subprocess.Popen", FakePopen) + + settings.set("GMAIL_ACCOUNT", "random@gmail.local") + settings.set("APP_VOLUME_TYPE", 11) + settings.set("NODES_NAMES", ["node-1"]) + settings.set("NODES_ZONES", ["zone-1"]) + + PromptGke(settings).prompt_gke() + assert settings.get("GOOGLE_NODE_HOME_DIR") == "/home/random" diff --git a/helm/tests/terminal/test_helm.py b/helm/tests/terminal/test_helm.py new file mode 100644 index 00000000000..852f69da0c2 --- /dev/null +++ b/helm/tests/terminal/test_helm.py @@ -0,0 +1,72 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + ("", "gluu"), # default + ("random", "random"), +]) +def test_helm_release_name(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.helm import PromptHelm + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("installer-settings.nginxIngress.releaseName", "ningress") + settings.set("installer-settings.nginxIngress.namespace", "ingress-nginx") + settings.set("opendj.multiCluster.enabled", False) + settings.set("installer-settings.releaseName", "") + + prompt = PromptHelm(settings) + prompt.prompt_helm() + assert settings.get("installer-settings.releaseName") == expected + + +@pytest.mark.parametrize("given, expected", [ + ("", "ningress"), # default + ("random", "random"), +]) +def test_helm_ingress_release_name(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.helm import PromptHelm + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("installer-settings.releaseName", "gluu") + settings.set("installer-settings.nginxIngress.namespace", "ingress-nginx") + settings.set("opendj.multiCluster.enabled", False) + settings.set("installer-settings.nginxIngress.releaseName", "") + + prompt = PromptHelm(settings) + prompt.prompt_helm() + assert settings.get("installer-settings.nginxIngress.releaseName") == expected + + +@pytest.mark.parametrize("given, expected", [ + ("", "ingress-nginx"), # default + ("random", "random"), +]) +def test_helm_ingress_namespace(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.helm import PromptHelm + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("installer-settings.nginxIngress.releaseName", "ningress") + settings.set("installer-settings.releaseName", "gluu") + settings.set("opendj.multiCluster.enabled", False) + settings.set("installer-settings.nginxIngress.namespace", "") + + prompt = PromptHelm(settings) + prompt.prompt_helm() + assert settings.get("installer-settings.nginxIngress.namespace") == expected + + +@pytest.mark.parametrize("given, expected", [ + (False, False), +]) +def test_aws_arn(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.helm import PromptHelm + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("global.cnPersistenceType", "ldap") + settings.set("opendj.multiCluster.enabled", False) + prompt = PromptHelm(settings) + prompt.prompt_helm() + assert settings.get("opendj.multiCluster.enabled") == expected diff --git a/helm/tests/terminal/test_helpers.py b/helm/tests/terminal/test_helpers.py new file mode 100644 index 00000000000..5ac5f3fa9b1 --- /dev/null +++ b/helm/tests/terminal/test_helpers.py @@ -0,0 +1,55 @@ +import pytest +import click +from pygluu.kubernetes.terminal.helpers import gather_ip +import pygluu.kubernetes.terminal.helpers as module0 +import logging + + +@pytest.mark.parametrize("given, expected", [ + (True, True), + (False, False), +]) +def test_confirm_ip(monkeypatch, given, expected): + + monkeypatch.setattr("click.confirm", lambda x: given) + assert click.confirm("Random question") == expected + + +def test_list_nodes_ip(monkeypatch, settings): + gather_ip = "22.22.22.22" + monkeypatch.setattr("click.prompt", lambda x, default: gather_ip) + + settings.set("global.storageClass.provisioner", "kubernetes.io/aws-ebs") + assert gather_ip == gather_ip + + +def test_k8s_node_address(monkeypatch, settings): + gather_ip = "22.22.22.22" + monkeypatch.setattr("click.prompt", lambda x, default: gather_ip) + settings.set("global.storageClass.provisioner", "kubernetes.io/aws-ebs") + assert gather_ip == gather_ip + + +def test_list_nodes_ip(caplog, settings): + # set collection to something that is not a collection + gather_ip = "22.22.22.22" + settings.set("global.storageClass.provisioner", "kubernetes.io/aws-ebs") + + with caplog.at_level(logging.INFO): + assert gather_ip == gather_ip + + +def test_unode_ip_list(caplog, settings): + # set collection to something that is not a collection + gather_ip = "22.22.22.22" + settings.set("global.storageClass.provisioner", "kubernetes.io/aws-ebs") + + with caplog.at_level(logging.INFO): + assert gather_ip == gather_ip + + +def test_base_exception(): + try: + var0 = module0.gather_ip() + except BaseException: + pass diff --git a/helm/tests/terminal/test_images.py b/helm/tests/terminal/test_images.py new file mode 100644 index 00000000000..0d3339c2871 --- /dev/null +++ b/helm/tests/terminal/test_images.py @@ -0,0 +1,114 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + (True, True), +]) +def test_testenv_prompt_test_edit_casa(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.images import PromptImages + + monkeypatch.setattr("click.prompt", lambda x, default: given) + settings.set("installer-settings.images.edit", True) + settings.set("config.configmap.cnCasaEnabled", True) + prompt = PromptImages(settings) + prompt.prompt_image_name_tag() + assert settings.get("casa.image.tag") == expected + + +@pytest.mark.parametrize("given, expected", [ + (True, True), +]) +def test_testenv_prompt_test_edit_crrotate(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.images import PromptImages + + monkeypatch.setattr("click.prompt", lambda x, default: given) + settings.set("installer-settings.images.edit", True) + settings.set("global.cr-rotate.enabled", True) + prompt = PromptImages(settings) + prompt.prompt_image_name_tag() + assert settings.get("cr-rotate.image.tag") == expected + + +@pytest.mark.parametrize("given, expected", [ + (True, True), +]) +def test_testenv_prompt_test_edit_keyauth(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.images import PromptImages + + monkeypatch.setattr("click.prompt", lambda x, default: given) + settings.set("installer-settings.images.edit", True) + settings.set("global.auth-server-key-rotation.enabled", True) + prompt = PromptImages(settings) + prompt.prompt_image_name_tag() + assert settings.get("auth-server-key-rotation.image.tag") == expected + + +@pytest.mark.parametrize("given, expected", [ + (True, True), +]) +def test_testenv_prompt_test_edit_hybrid(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.images import PromptImages + + monkeypatch.setattr("click.prompt", lambda x, default: given) + settings.set("installer-settings.images.edit", True) + settings.set("config.configmap.cnCacheType", "hybrid") + prompt = PromptImages(settings) + prompt.prompt_image_name_tag() + assert settings.get("opendj.image.tag") == expected + + +@pytest.mark.parametrize("given, expected", [ + (True, True), +]) +def test_testenv_prompt_test_edit_ldap(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.images import PromptImages + + monkeypatch.setattr("click.prompt", lambda x, default: given) + settings.set("installer-settings.images.edit", True) + settings.set("config.configmap.cnCacheType", "ldap") + prompt = PromptImages(settings) + prompt.prompt_image_name_tag() + assert settings.get("opendj.image.tag") == expected + + +@pytest.mark.parametrize("given, expected", [ + (True, True), +]) +def test_testenv_prompt_test_edit_clientapi(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.images import PromptImages + + monkeypatch.setattr("click.prompt", lambda x, default: given) + settings.set("installer-settings.images.edit", True) + settings.set("global.client-api.enabled", True) + prompt = PromptImages(settings) + prompt.prompt_image_name_tag() + assert settings.get("client-api.image.tag") == expected + + +@pytest.mark.parametrize("given, expected", [ + (True, True), +]) +def test_testenv_prompt_test_edit_oxpassport(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.images import PromptImages + + monkeypatch.setattr("click.prompt", lambda x, default: given) + settings.set("installer-settings.images.edit", True) + settings.set("config.configmap.cnPassportEnabled", True) + prompt = PromptImages(settings) + prompt.prompt_image_name_tag() + assert settings.get("oxpassport.image.tag") == expected + + +@pytest.mark.parametrize("given, expected", [ + (True, True), +]) +def test_testenv_prompt_test_edit_oxshiboleth(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.images import PromptImages + + monkeypatch.setattr("click.prompt", lambda x, default: given) + settings.set("installer-settings.images.edit", True) + settings.set("global.oxshibboleth.enabled", True) + prompt = PromptImages(settings) + prompt.prompt_image_name_tag() + assert settings.get("oxshibboleth.image.tag") == expected + diff --git a/helm/tests/terminal/test_istio.py b/helm/tests/terminal/test_istio.py new file mode 100644 index 00000000000..721dbf16077 --- /dev/null +++ b/helm/tests/terminal/test_istio.py @@ -0,0 +1,67 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + (False, False), + (True, True), +]) +def test_istio_ingress(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.istio import PromptIstio + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("global.istio.ingress", "") + settings.set("global.storageClass.provisioner", "kubernetes.io/azure-disk") + prompt = PromptIstio(settings) + prompt.prompt_istio() + assert settings.get("global.istio.ingress") == expected + + +def test_istio_enabled_prompt(monkeypatch, settings): + from pygluu.kubernetes.terminal.istio import PromptIstio + + monkeypatch.setattr("click.prompt", lambda x, default: True) + + settings.set("global.istio.ingress", True) + settings.set("global.istio.enabled", "False") + prompt = PromptIstio(settings) + prompt.prompt_istio() + assert settings.get("global.istio.enabled") + + +@pytest.mark.parametrize("given, expected", [ + (False, False), + (True, True), +]) +def test_global_istio_enabled(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.istio import PromptIstio + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("global.istio.enabled", "") + prompt = PromptIstio(settings) + prompt.prompt_istio() + assert settings.get("global.istio.enabled") == expected + + +def test_istio_namespace(monkeypatch, settings): + from pygluu.kubernetes.terminal.istio import PromptIstio + + monkeypatch.setattr("click.prompt", lambda x, default: "istio-system") + + settings.set("global.istio.namespace", "") + settings.set("global.istio.enabled", True) + prompt = PromptIstio(settings) + prompt.prompt_istio() + assert settings.get("global.istio.namespace") == "istio-system" + + +def test_istio_lbaddr(monkeypatch, settings): + from pygluu.kubernetes.terminal.istio import PromptIstio + + monkeypatch.setattr("click.prompt", lambda x, default: "") + + settings.set("global.istio.namespace", "") + settings.set("global.istio.enabled", True) + settings.set("config.configmap.lbAddr", "") + prompt = PromptIstio(settings) + prompt.prompt_istio() + assert settings.get("config.configmap.lbAddr") == "" diff --git a/helm/tests/terminal/test_jackrabbit.py b/helm/tests/terminal/test_jackrabbit.py new file mode 100644 index 00000000000..23c6c640b6e --- /dev/null +++ b/helm/tests/terminal/test_jackrabbit.py @@ -0,0 +1,100 @@ +import pytest + + +def test_jackrabbit_enable(monkeypatch, settings): + from pygluu.kubernetes.terminal.jackrabbit import PromptJackrabbit + + monkeypatch.setattr("click.confirm", lambda x, default: True) + + settings.set("jackrabbit.secrets.cnJackrabbitAdminPassword", "Test1234#") + settings.set("config.configmap.cnJackrabbitAdminId", "admin") + settings.set("jackrabbit.storage.size", "4Gi") + settings.set("global.jackrabbit.enabled", "") + + prompt = PromptJackrabbit(settings) + prompt.prompt_jackrabbit() + + assert settings.get("global.jackrabbit.enabled") + assert settings.get("jackrabbit.storage.size") == "4Gi" + assert settings.get("config.configmap.cnJackrabbitUrl") == "http://jackrabbit:8080" + assert settings.get("config.configmap.cnJackrabbitAdminId") == "admin" + assert settings.get("jackrabbit.secrets.cnJackrabbitAdminPassword") == "Test1234#" + + +def test_jackrabbit_disable_no_url(monkeypatch, settings): + from pygluu.kubernetes.terminal.jackrabbit import PromptJackrabbit + + monkeypatch.setattr("click.confirm", lambda x, default: False) + monkeypatch.setattr("click.prompt", lambda x, default: "http://jackrabbit:8080") + + settings.set("config.configmap.cnJackrabbitAdminId", "admin") + settings.set("jackrabbit.secrets.cnJackrabbitAdminPassword", "Test1234#") + settings.set("installer-settings.jackrabbit.clusterMode", "N") + settings.set("config.configmap.cnJackrabbitUrl", "") + + prompt = PromptJackrabbit(settings) + prompt.prompt_jackrabbit() + + assert settings.get("config.configmap.cnJackrabbitUrl") == "http://jackrabbit:8080" + + +def test_jackrabit_adminid(monkeypatch, settings): + from pygluu.kubernetes.terminal.jackrabbit import PromptJackrabbit + + monkeypatch.setattr("click.prompt", lambda x, default: "admin") + + settings.set("config.configmap.cnJackrabbitAdminId", "") + prompt = PromptJackrabbit(settings) + prompt.prompt_jackrabbit() + assert settings.get("config.configmap.cnJackrabbitAdminId") == "admin" + + +@pytest.mark.parametrize("given, expected", [ + (False, False), + (True, True), +]) +def test_testenv_prompt_test_environment(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.jackrabbit import PromptJackrabbit + + monkeypatch.setattr("click.confirm", lambda x, default: given) + settings.set("installer-settings.postgres.namespace", "test") + settings.set("installer-settings.jackrabbit.clusterMode", "") + prompt = PromptJackrabbit(settings) + prompt.prompt_jackrabbit() + assert settings.get("installer-settings.jackrabbit.clusterMode") == expected + + +def test_jackrabit_postgresdb(monkeypatch, settings): + from pygluu.kubernetes.terminal.jackrabbit import PromptJackrabbit + + monkeypatch.setattr("click.prompt", lambda x, default: "jackrabbit") + settings.set("installer-settings.postgres.install", True) + settings.set("installer-settings.jackrabbit.clusterMode", True) + settings.set("config.configmap.cnJackrabbitPostgresDatabaseName", "") + prompt = PromptJackrabbit(settings) + prompt.prompt_jackrabbit() + assert settings.get("config.configmap.cnJackrabbitPostgresDatabaseName") == "jackrabbit" + + +def test_jackrabit_postgresuser(monkeypatch, settings): + from pygluu.kubernetes.terminal.jackrabbit import PromptJackrabbit + + monkeypatch.setattr("click.prompt", lambda x, default: "jackrabbit") + settings.set("installer-settings.postgres.install", True) + settings.set("installer-settings.jackrabbit.clusterMode", True) + settings.set("config.configmap.cnJackrabbitPostgresUser", "") + prompt = PromptJackrabbit(settings) + prompt.prompt_jackrabbit() + assert settings.get("config.configmap.cnJackrabbitPostgresUser") == "jackrabbit" + + +def test_jackrabit_postgressize(monkeypatch, settings): + from pygluu.kubernetes.terminal.jackrabbit import PromptJackrabbit + + monkeypatch.setattr("click.prompt", lambda x, default: "4Gi") + + settings.set("global.jackrabbit.enabled", True) + settings.set("jackrabbit.storage.size", "") + prompt = PromptJackrabbit(settings) + prompt.prompt_jackrabbit() + assert settings.get("jackrabbit.storage.size") == "4Gi" diff --git a/helm/tests/terminal/test_ldap.py b/helm/tests/terminal/test_ldap.py new file mode 100644 index 00000000000..697731fd442 --- /dev/null +++ b/helm/tests/terminal/test_ldap.py @@ -0,0 +1,20 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + (1, "default"), + (2, "user"), + (3, "site"), + (4, "cache"), + (5, "token"), + (6, "session"), + (0, "default"), +]) +def test_prompt_hybrid_ldap_held_data(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.ldap import PromptLdap + + monkeypatch.setattr("click.prompt", lambda x, default: given) + + settings.set("config.configmap.cnPersistenceLdapMapping", "") + PromptLdap(settings).prompt_hybrid_ldap_held_data() + assert settings.get("config.configmap.cnPersistenceLdapMapping") == expected diff --git a/helm/tests/terminal/test_license.py b/helm/tests/terminal/test_license.py new file mode 100644 index 00000000000..28ce24378df --- /dev/null +++ b/helm/tests/terminal/test_license.py @@ -0,0 +1,19 @@ +import pytest + + +def test_license_accepted(monkeypatch, settings): + from pygluu.kubernetes.terminal.license import PromptLicense + + monkeypatch.setattr("click.confirm", lambda x: True) + + PromptLicense(settings) + assert settings.get("installer-settings.acceptLicense") + + +def test_license_rejected(monkeypatch, settings): + from pygluu.kubernetes.terminal.license import PromptLicense + + monkeypatch.setattr("click.confirm", lambda x: False) + + with pytest.raises(SystemExit): + PromptLicense(settings) diff --git a/helm/tests/terminal/test_namespace.py b/helm/tests/terminal/test_namespace.py new file mode 100644 index 00000000000..280d7536a33 --- /dev/null +++ b/helm/tests/terminal/test_namespace.py @@ -0,0 +1,15 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + ("", "gluu"), + ("my-ns", "my-ns"), +]) +def test_gluu_namespace(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.namespace import PromptNamespace + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + settings.set("installer-settings.namespace", "") + prompt = PromptNamespace(settings) + prompt.prompt_gluu_namespace() + assert settings.get("installer-settings.namespace") == expected diff --git a/helm/tests/terminal/test_openbanking.py b/helm/tests/terminal/test_openbanking.py new file mode 100644 index 00000000000..35f926e84f3 --- /dev/null +++ b/helm/tests/terminal/test_openbanking.py @@ -0,0 +1,30 @@ +import pytest +from unittest.mock import patch, mock_open + + +@pytest.mark.parametrize("given, expected", [ + ("", "https://keystore.openbankingtest.org.uk/keystore/openbanking.jwks"), # default + ("random", "random"), +]) +def test_ob_external_jwks(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.openbanking import PromptOpenBanking + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("global.cnObExtSigningJwksUri", "") + settings.set("global.cnObExtSigningJwksCrt", "random") + settings.set("global.cnObExtSigningJwksKey", "random") + settings.set("global.cnObExtSigningJwksKeyPassPhrase", "random") + settings.set("global.cnObExtSigningAlias", "random") + settings.set("global.cnObStaticSigningKeyKid", "random") + settings.set("global.cnObTransportCrt", "random") + settings.set("global.cnObTransportKey", "random") + settings.set("global.cnObTransportKeyPassPhrase", "random") + settings.set("global.cnObTransportAlias", "random") + settings.set("installer-settings.openbanking.hasCnObTransportTrustStore", True) + settings.set("global.cnObTransportTrustStore", "random") + + prompt = PromptOpenBanking(settings) + prompt.prompt_openbanking() + + assert settings.get("global.cnObExtSigningJwksUri") == expected diff --git a/helm/tests/terminal/test_optionalservices.py b/helm/tests/terminal/test_optionalservices.py new file mode 100644 index 00000000000..9fa27ae3859 --- /dev/null +++ b/helm/tests/terminal/test_optionalservices.py @@ -0,0 +1,141 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + (True, True), +]) +def test_prompt_casa(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.optionalservices import PromptOptionalServices + + monkeypatch.setattr("click.confirm", lambda x: given) + + settings.set("config.configmap.cnCasaEnabled", True) + settings.set("global.client-api.enabled", "") + prompt = PromptOptionalServices(settings) + prompt.prompt_optional_services() + assert settings.get("global.client-api.enabled") == expected + + +@pytest.mark.parametrize("given, expected", [ + (False, False), + (True, True), +]) +def test_testenv_prompt_crrotate(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.optionalservices import PromptOptionalServices + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("global.cr-rotate.enabled", "") + prompt = PromptOptionalServices(settings) + prompt.prompt_optional_services() + assert settings.get("global.cr-rotate.enabled") == expected + + +@pytest.mark.parametrize("given, expected", [ + (False, False), +]) +def test_testenv_kyerotation(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.optionalservices import PromptOptionalServices + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("global.auth-server-key-rotation.enabled", "") + prompt = PromptOptionalServices(settings) + prompt.prompt_optional_services() + assert settings.get("global.auth-server-key-rotation.enabled") == expected + + +@pytest.mark.parametrize("given, expected", [ + (False, False), + (True, True), +]) +def test_testenv_prompt_passport(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.optionalservices import PromptOptionalServices + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("config.configmap.cnPassportEnabled", "") + prompt = PromptOptionalServices(settings) + prompt.prompt_optional_services() + assert settings.get("config.configmap.cnPassportEnabled") == expected + + +@pytest.mark.parametrize("given, expected", [ + (False, False), + (True, True), +]) +def test_testenv_prompt_cncasat(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.optionalservices import PromptOptionalServices + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("config.configmap.cnCasaEnabled", "") + prompt = PromptOptionalServices(settings) + prompt.prompt_optional_services() + assert settings.get("config.configmap.cnCasaEnabled") == expected + + +@pytest.mark.parametrize("given, expected", [ + (False, False), + (True, True), +]) +def test_testenv_prompt_oxshibboleth(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.optionalservices import PromptOptionalServices + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("global.oxshibboleth.enabled", "") + prompt = PromptOptionalServices(settings) + prompt.prompt_optional_services() + assert settings.get("global.oxshibboleth.enabled") == expected + + +@pytest.mark.parametrize("given, expected", [ + (False, False), + (True, True), +]) +def test_testenv_prompt_fido2(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.optionalservices import PromptOptionalServices + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("global.fido2.enabled", "") + prompt = PromptOptionalServices(settings) + prompt.prompt_optional_services() + assert settings.get("global.fido2.enabled") == expected + + +@pytest.mark.parametrize("given, expected", [ + (False, False), + (True, True), +]) +def test_testenv_prompt_configapit(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.optionalservices import PromptOptionalServices + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("global.config-api.enabled", "") + prompt = PromptOptionalServices(settings) + prompt.prompt_optional_services() + assert settings.get("global.config-api.enabled") == expected + + +@pytest.mark.parametrize("given, expected", [ + (False, False), + (True, True), +]) +def test_testenv_prompt_scim(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.optionalservices import PromptOptionalServices + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("global.scim.enabled", "") + prompt = PromptOptionalServices(settings) + prompt.prompt_optional_services() + assert settings.get("global.scim.enabled") == expected + + +@pytest.mark.parametrize("given, expected", [ + (False, False), + (True, True), +]) +def test_testenv_prompt_clientapi(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.optionalservices import PromptOptionalServices + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("global.client-api.enabled", "") + prompt = PromptOptionalServices(settings) + prompt.prompt_optional_services() + assert settings.get("global.client-api.enabled") == expected diff --git a/helm/tests/terminal/test_persistencebackend.py b/helm/tests/terminal/test_persistencebackend.py new file mode 100644 index 00000000000..da73578adfb --- /dev/null +++ b/helm/tests/terminal/test_persistencebackend.py @@ -0,0 +1,29 @@ +def test_prompt_persistence_backend_ldap(monkeypatch, settings): + from pygluu.kubernetes.terminal.persistencebackend import PromptPersistenceBackend + + monkeypatch.setattr("click.prompt", lambda x, default: 1) + + settings.set("global.cnPersistenceType", "") + PromptPersistenceBackend(settings).prompt_persistence_backend() + + assert settings.get("global.cnPersistenceType") == "ldap" + + +def test_prompt_persistence_backend_couchbase(monkeypatch, settings): + from pygluu.kubernetes.terminal.persistencebackend import PromptPersistenceBackend + + monkeypatch.setattr("click.prompt", lambda x, default: 2) + + settings.set("global.cnPersistenceType", "") + PromptPersistenceBackend(settings).prompt_persistence_backend() + assert settings.get("global.cnPersistenceType") == "couchbase" + + +def test_prompt_persistence_backend_hybrid(monkeypatch, settings): + from pygluu.kubernetes.terminal.persistencebackend import PromptPersistenceBackend + + monkeypatch.setattr("click.prompt", lambda x, default: 3) + + settings.set("global.cnPersistenceType", "") + PromptPersistenceBackend(settings).prompt_persistence_backend() + assert settings.get("global.cnPersistenceType") == "hybrid" diff --git a/helm/tests/terminal/test_postgres.py b/helm/tests/terminal/test_postgres.py new file mode 100644 index 00000000000..f6102d1a982 --- /dev/null +++ b/helm/tests/terminal/test_postgres.py @@ -0,0 +1,45 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + ("", "postgres"), # default + ("random", "random"), +]) +def test_postgres_namespace(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.postgres import PromptPostgres + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + settings.set("installer-settings.postgres.install", True) + settings.set("config.configmap.cnJackrabbitPostgresHost", "postgres.postgres.svc.cluster.local") + settings.set("installer-settings.postgres.namespace", "") + + prompt = PromptPostgres(settings) + prompt.prompt_postgres() + assert settings.get("installer-settings.postgres.namespace") == expected + + +@pytest.mark.parametrize("given, expected", [ + ("", "postgresql.jackrabbitpostgres.svc.cluster.local")]) +def test_postgres_url(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.postgres import PromptPostgres + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + settings.set("installer-settings.postgres.install", True) + settings.set("installer-settings.postgres.namespace", "postgres") + settings.set("config.configmap.cnJackrabbitPostgresHost", "") + + prompt = PromptPostgres(settings) + prompt.prompt_postgres() + assert settings.get("config.configmap.cnJackrabbitPostgresHost") == expected + + +def test_prompt_postgres_install(monkeypatch, settings): + from pygluu.kubernetes.terminal.postgres import PromptPostgres + + monkeypatch.setattr("click.confirm", lambda x, default: True) + settings.set("installer-settings.postgres.namespace", "postgres") + settings.set("installer-settings.postgres.install", "") + prompt = PromptPostgres(settings) + prompt.prompt_postgres() + + assert settings.get("installer-settings.postgres.install") == True diff --git a/helm/tests/terminal/test_prompt.py b/helm/tests/terminal/test_prompt.py new file mode 100644 index 00000000000..5e06723ece2 --- /dev/null +++ b/helm/tests/terminal/test_prompt.py @@ -0,0 +1,208 @@ +import pytest +from pygluu.kubernetes.terminal.prompt import Prompt +check = Prompt() + + +def test_license(monkeypatch, settings): + + monkeypatch.setattr("click.confirm", lambda x: True) + + settings.set("installer-settings.acceptLicense", "Y") + check.license() + assert settings.get("installer-settings.acceptLicense") + + +@pytest.mark.skip(reason="this test needs fixing") +def test_versions(settings): + + settings.set("installer-settings.currentVersion", "5.2") + check.versions() + assert settings.get("installer-settings.currentVersion") == "5.2" + + +@pytest.mark.parametrize("given, expected", [ + (1, "microk8s.io/hostpath"), +]) +def test_arch(monkeypatch, settings, given, expected): + + monkeypatch.setattr("click.prompt", lambda x, default: given) + + settings.set("global.storageClass.provisioner", "microk8s.io/hostpath") + check.arch() + assert settings.get("global.storageClass.provisioner") == expected + + +@pytest.mark.parametrize("given, expected", [ + ("", "gluu"), +]) +def test_namespace(monkeypatch, settings, given, expected): + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + settings.set("installer-settings.namespace", "gluu") + check.namespace() + assert settings.get("installer-settings.namespace") == expected + + +@pytest.mark.parametrize("given, expected", [ + (True, True), +]) +def test_optional_services(monkeypatch, settings, given, expected): + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("config.configmap.cnPassportEnabled", True) + check.optional_services() + assert settings.get("config.configmap.cnPassportEnabled") + + +def test_istio(monkeypatch, settings): + + monkeypatch.setattr("click.prompt", lambda x, default: "istio-system") + + settings.set("global.istio.namespace", "istio-system") + settings.set("global.istio.enabled", True) + check.istio() + assert settings.get("global.istio.namespace") == "istio-system" + + +def test_jackrabbit(monkeypatch, settings): + + monkeypatch.setattr("click.prompt", lambda x, default: "admin") + + settings.set("config.configmap.cnJackrabbitAdminId", "admin") + check.jackrabbit() + assert settings.get("config.configmap.cnJackrabbitAdminId") == "admin" + + +def test_persistence_backend(monkeypatch, settings): + + monkeypatch.setattr("click.prompt", lambda x, default: "hybrid") + + settings.set("global.cnPersistenceType", "hybrid") + check.persistence_backend() + assert settings.get("global.cnPersistenceType") == "hybrid" + + +@pytest.mark.parametrize("given, expected", [ + ("", "NATIVE_PERSISTENCE"), # default +]) +def test_cache(monkeypatch, settings, given, expected): + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + monkeypatch.setattr("pygluu.kubernetes.terminal.redis.PromptRedis.prompt_redis", lambda x: None) + settings.set("config.configmap.cnCacheType", "NATIVE_PERSISTENCE") + + check.cache() + assert settings.get("config.configmap.cnCacheType") == "NATIVE_PERSISTENCE" + + +def test_confirm_settings(monkeypatch, settings): + + monkeypatch.setattr("click.confirm", lambda x: True) + + settings.set("installer-settings.confirmSettings", True) + check.confirm_settings() + assert settings.get("installer-settings.confirmSettings") + + +def test_replicas(monkeypatch, settings): + + monkeypatch.setattr("click.prompt", lambda x, default: 1) + + settings.set("auth-server.replicas", 1) + check.replicas() + assert settings.get("auth-server.replicas") == 1 + + +@pytest.mark.skip(reason="this test needs fixing") +@pytest.mark.parametrize("given, expected", [ + ("", "demoexample.gluu.org"), # default +]) +def test_configuration(monkeypatch, settings, given, expected): + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("config.countryCode", "US") + settings.set("config.state", "TX") + settings.set("config.city", "Austin") + settings.set("config.email", "support@gluu.org") + settings.set("config.orgName", "Gluu") + settings.set("config.adminPassword", "Admin GUI") + settings.set("global.fqdn", "demoexample.gluu.org") + check.configuration() + assert settings.get("global.fqdn") == expected + + +@pytest.mark.parametrize("given, expected", [ + (False, False), +]) +def test_images(monkeypatch, settings, given, expected): + + monkeypatch.setattr("click.prompt", lambda x, default: given) + settings.set("installer-settings.images.edit", True) + settings.set("config.configmap.cnCacheType", "ldap") + check.images() + assert settings.get("opendj.image.tag") == expected + + +@pytest.mark.parametrize("given, expected", [ + (False, False), +]) +def test_test_environment(monkeypatch, settings, given, expected): + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("global.cloud.testEnviroment", False) + settings.set("global.storageClass.provisioner", "awsEbsDynamic") + check.test_enviornment() + assert settings.get("global.cloud.testEnviroment") == expected + + +def test_ldap(monkeypatch, settings): + + monkeypatch.setattr("click.prompt", lambda x, default: "default") + + settings.set("config.configmap.cnPersistenceLdapMapping", "default") + settings.set("global.cnPersistenceType", "hybrid") + check.ldap() + assert settings.get("config.configmap.cnPersistenceLdapMapping") == "default" + + +def test_volume(settings, monkeypatch): + monkeypatch.setattr("click.prompt", lambda x, default: "microk8s.io/hostpath") + + settings.set("installer-settings.volumeProvisionStrategy", "microk8sDynamic") + settings.set("global.storageClass.provisioner", "microk8s.io/hostpath") + check.volumes() + assert settings.get("global.storageClass.provisioner") == "microk8s.io/hostpath" + + +def test_couchbase(monkeypatch, settings): + + monkeypatch.setattr("click.prompt", lambda x, default: "cbns") + + settings.set("installer-settings.couchbase.namespace", "cbns") + settings.set("global.cnPersistenceType", "couchbase") + check.couchbase() + assert settings.get("installer-settings.couchbase.namespace") == "cbns" + + +def test_backup(monkeypatch, settings): + + + monkeypatch.setattr("click.prompt", lambda x, default: "0 2 * * 6") + + settings.set("global.storageClass.provisioner", "awsEbsDynamic") + settings.set("global.cnPersistenceType", "couchbase") + settings.set("installer-settings.couchbase.backup.fullSchedule", "0 2 * * 6") + + check.backup() + + assert settings.get("installer-settings.couchbase.backup.fullSchedule") == "0 2 * * 6" + + +def test_confirms_settings(settings, monkeypatch): + + monkeypatch.setattr("click.confirm", lambda x: False) + monkeypatch.setattr("pygluu.kubernetes.terminal.prompt.Prompt.prompt", lambda x: None) + settings.set("installer-settings.confirmSettings", False) + check.confirm_settings() + assert settings.get("installer-settings.confirmSettings") == False \ No newline at end of file diff --git a/helm/tests/terminal/test_redis.py b/helm/tests/terminal/test_redis.py new file mode 100644 index 00000000000..231c8af7ad7 --- /dev/null +++ b/helm/tests/terminal/test_redis.py @@ -0,0 +1,43 @@ +import pygluu.kubernetes.terminal.redis as module0 +import click +import pytest + + +def test_prompt_redis_type(monkeypatch, settings): + from pygluu.kubernetes.terminal.redis import PromptRedis + + monkeypatch.setattr("click.prompt", lambda x, default: "CLUSTER") + + settings.set("config.configmap.cnRedisType", "") + prompt = PromptRedis(settings) + prompt.prompt_redis() + assert settings.get("config.configmap.cnRedisType") == "CLUSTER" + + +@pytest.mark.parametrize("given, expected", [ + ("", "redis.redis.svc.cluster.local"), # default + ("random", "random"), +]) +def test_redis_url(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.redis import PromptRedis + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + settings.set("installer-settings.redis.namespace", "redis") + settings.set("config.configmap.cnRedisUrl", "") + + prompt = PromptRedis(settings) + prompt.prompt_redis() + assert settings.get("config.configmap.cnRedisUrl") == expected + + +def test_prompt_redis_install(monkeypatch, settings): + from pygluu.kubernetes.terminal.redis import PromptRedis + + monkeypatch.setattr("click.confirm", lambda x: True) + + settings.set("installer-settings.redis.install", True) + prompt = PromptRedis(settings) + prompt.prompt_redis() + + assert settings.get("installer-settings.redis.install") \ No newline at end of file diff --git a/helm/tests/terminal/test_replicas.py b/helm/tests/terminal/test_replicas.py new file mode 100644 index 00000000000..1229960a26a --- /dev/null +++ b/helm/tests/terminal/test_replicas.py @@ -0,0 +1,103 @@ +import pytest + + +def test_prompt_replicas_auth_server(monkeypatch, settings): + from pygluu.kubernetes.terminal.replicas import PromptReplicas + + monkeypatch.setattr("click.prompt", lambda x, default: 1) + + settings.set("auth-server.replicas", "") + PromptReplicas(settings).prompt_replicas() + assert settings.get("auth-server.replicas") == 1 + + +def test_prompt_replicas_fido2(monkeypatch, settings): + from pygluu.kubernetes.terminal.replicas import PromptReplicas + + monkeypatch.setattr("click.prompt", lambda x, default: 1) + + # bypass + settings.set("fido2.replicas", "") + + settings.set("global.fido2.enabled", "Y") + PromptReplicas(settings).prompt_replicas() + assert settings.get("fido2.replicas") == 1 + + +def test_prompt_replicas_scim(monkeypatch, settings): + from pygluu.kubernetes.terminal.replicas import PromptReplicas + + monkeypatch.setattr("click.prompt", lambda x, default: 1) + + # bypass + settings.set("scim.replicas", "") + + settings.set("global.scim.enabled", "Y") + PromptReplicas(settings).prompt_replicas() + assert settings.get("scim.replicas") == 1 + + +@pytest.mark.parametrize("type_", ["ldap", "hybrid"]) +def test_prompt_replicas_persistence(monkeypatch, settings, type_): + from pygluu.kubernetes.terminal.replicas import PromptReplicas + + monkeypatch.setattr("click.prompt", lambda x, default: 1) + + # bypass + settings.set("opendj.replicas", "") + + settings.set("global.cnPersistenceType", type_) + PromptReplicas(settings).prompt_replicas() + assert settings.get("opendj.replicas") == 1 + + +def test_prompt_replicas_oxshibboleth(monkeypatch, settings): + from pygluu.kubernetes.terminal.replicas import PromptReplicas + + monkeypatch.setattr("click.prompt", lambda x, default: 1) + + # bypass + settings.set("oxshibboleth.replicas", "") + + settings.set("global.oxshibboleth.enabled", "Y") + PromptReplicas(settings).prompt_replicas() + assert settings.get("oxshibboleth.replicas") == 1 + + +def test_prompt_replicas_oxpassport(monkeypatch, settings): + from pygluu.kubernetes.terminal.replicas import PromptReplicas + + monkeypatch.setattr("click.prompt", lambda x, default: 1) + + # bypass + settings.set("oxpassport.replicas", "") + + settings.set("config.configmap.cnPassportEnabled", "Y") + PromptReplicas(settings).prompt_replicas() + assert settings.get("oxpassport.replicas") == 1 + + +def test_prompt_replicas_client_api(monkeypatch, settings): + from pygluu.kubernetes.terminal.replicas import PromptReplicas + + monkeypatch.setattr("click.prompt", lambda x, default: 1) + + # bypass + settings.set("client-api.replicas", "") + + settings.set("global.client-api.enabled", "Y") + PromptReplicas(settings).prompt_replicas() + assert settings.get("client-api.replicas") == 1 + + +def test_prompt_replicas_casa(monkeypatch, settings): + from pygluu.kubernetes.terminal.replicas import PromptReplicas + + monkeypatch.setattr("click.prompt", lambda x, default: 1) + + # bypass + settings.set("casa.replicas", "") + + settings.set("config.configmap.cnCasaEnabled", "Y") + PromptReplicas(settings).prompt_replicas() + assert settings.get("casa.replicas") == 1 diff --git a/helm/tests/terminal/test_testenv.py b/helm/tests/terminal/test_testenv.py new file mode 100644 index 00000000000..8a327c7b9b2 --- /dev/null +++ b/helm/tests/terminal/test_testenv.py @@ -0,0 +1,14 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + (False, False), + (True, True), +]) +def test_testenv_prompt_test_environment(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.testenv import PromptTestEnvironment + + monkeypatch.setattr("click.confirm", lambda x: given) + settings.set("global.cloud.testEnviroment", "") + PromptTestEnvironment(settings).prompt_test_environment() + assert settings.get("global.cloud.testEnviroment") == expected diff --git a/helm/tests/terminal/test_upgrade.py b/helm/tests/terminal/test_upgrade.py new file mode 100644 index 00000000000..f9349e9df52 --- /dev/null +++ b/helm/tests/terminal/test_upgrade.py @@ -0,0 +1,20 @@ +import pytest + + +@pytest.mark.parametrize("given, expected", [ + ("", "5.0"), + ("5.0", "5.0"), +]) +def test_upgrade_version(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.upgrade import PromptUpgrade + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + monkeypatch.setattr( + "pygluu.kubernetes.terminal.images.PromptImages.prompt_image_name_tag", + lambda cls: None, + ) + + settings.set("installer-settings.upgrade.targetVersion", "") + PromptUpgrade(settings).prompt_upgrade() + assert settings.get("installer-settings.upgrade.targetVersion") == expected + assert settings.get("installer-settings.image.edit") == "" diff --git a/helm/tests/terminal/test_version.py b/helm/tests/terminal/test_version.py new file mode 100644 index 00000000000..529d96c93e2 --- /dev/null +++ b/helm/tests/terminal/test_version.py @@ -0,0 +1,51 @@ +import contextlib +import os + +import pytest + + +def test_version_no_prompt(settings): + from pygluu.kubernetes.terminal.version import PromptVersion + + prompt = PromptVersion(settings, version="5.0") + prompt.prompt_version() + assert settings.get("installer-settings.currentVersion") == "5.0" + + +@pytest.mark.parametrize("given, expected", [ + ("", "5.0.0_01"), # default if empty + ("5.0.0_dev", "5.0.0_dev"), # non-empty shouldn't be overriden +]) +def test_version_merge_names_tags(settings, given, expected): + import json + from pygluu.kubernetes.terminal.version import PromptVersion + + with open("./gluu_versions.json", "w") as f: + json.dump({"5.0": {"LDAP_IMAGE_TAG": "5.0.0_01"}}, f) + + settings.set("installer-settings.currentVersion", "5.0") + settings.set("LDAP_IMAGE_TAG", given) + + PromptVersion(settings) + assert settings.get("LDAP_IMAGE_TAG") == expected + + with contextlib.suppress(FileNotFoundError): + os.unlink("./gluu_versions.json") + + +@pytest.mark.parametrize("given, expected", [ + ("", "5.0"), + ("5.0", "5.0"), +]) +def test_version(monkeypatch, settings, given, expected): + from pygluu.kubernetes.terminal.version import PromptVersion + + monkeypatch.setattr("click.prompt", lambda x, default: given or expected) + + prompt = PromptVersion(settings) + + # unset CN_VERSION in order to prompt user-input + settings.set("installer-settings.currentVersion", "") + + prompt.prompt_version() + assert settings.get("installer-settings.currentVersion") == expected diff --git a/helm/tests/terminal/test_volumes.py b/helm/tests/terminal/test_volumes.py new file mode 100644 index 00000000000..a15e0af7c53 --- /dev/null +++ b/helm/tests/terminal/test_volumes.py @@ -0,0 +1,179 @@ +import pytest + + +@pytest.mark.parametrize("arch, vol_type", [ + ("kubernetes.io/aws-ebs", 7), + ("kubernetes.io/gce-pd", 12), + ("kubernetes.io/azure-disk", 17), + ("dobs.csi.digitalocean.com", 22), + ("openebs.io/local", 26), +]) +def test_prompt_app_volume_type(monkeypatch, settings, arch, vol_type): + from pygluu.kubernetes.terminal.volumes import PromptVolumes + + monkeypatch.setattr("click.prompt", lambda x, default: vol_type) + + settings.set("global.cnPersistenceType", arch) + settings.set("CN_APP_VOLUME_TYPE", vol_type) + prompt = PromptVolumes(settings) + prompt.prompt_app_volume_type() + assert settings.get("CN_APP_VOLUME_TYPE") == vol_type + + +@pytest.mark.parametrize("vol_choice, vol_path", [ + (7, "awsEbsDynamic"), +]) +def test_prompt_app_volume_choice_aws(monkeypatch, settings, vol_choice, vol_path): + from pygluu.kubernetes.terminal.volumes import PromptVolumes + + monkeypatch.setattr("click.prompt", lambda x, default: vol_choice) + + settings.set("global.storageClass.provisioner", "kubernetes.io/aws-ebs") + settings.set("installer-settings.volumeProvisionStrategy", "awsEbsDynamic") + prompt = PromptVolumes(settings) + prompt.prompt_app_volume_type() + assert settings.get("installer-settings.volumeProvisionStrategy") == vol_path + + +@pytest.mark.parametrize("vol_choice, vol_path", [ + (12, "gkePdDynamic"), +]) +def test_prompt_app_volume_choice_gce(monkeypatch, settings, vol_choice, vol_path): + from pygluu.kubernetes.terminal.volumes import PromptVolumes + + monkeypatch.setattr("click.prompt", lambda x, default: vol_choice) + + settings.set("global.storageClass.provisioner", "kubernetes.io/gce-pd") + settings.set("installer-settings.volumeProvisionStrategy", "gkePdDynamic") + prompt = PromptVolumes(settings) + prompt.prompt_app_volume_type() + assert settings.get("installer-settings.volumeProvisionStrategy") == vol_path + + +@pytest.mark.parametrize("vol_choice, vol_path", [ + (17, "aksPdDynamic"), +]) +def test_prompt_app_volume_choice_azure(monkeypatch, settings, vol_choice, vol_path): + from pygluu.kubernetes.terminal.volumes import PromptVolumes + + monkeypatch.setattr("click.prompt", lambda x, default: vol_choice) + + settings.set("global.storageClass.provisioner", "kubernetes.io/azure-disk") + settings.set("installer-settings.volumeProvisionStrategy", "aksPdDynamic") + prompt = PromptVolumes(settings) + prompt.prompt_app_volume_type() + assert settings.get("installer-settings.volumeProvisionStrategy") == vol_path + + +@pytest.mark.parametrize("vol_choice, vol_path", [ + (22, "doksPdDynamic"), +]) +def test_prompt_app_volume_choice_do(monkeypatch, settings, vol_choice, vol_path): + from pygluu.kubernetes.terminal.volumes import PromptVolumes + + monkeypatch.setattr("click.prompt", lambda x, default: vol_choice) + + settings.set("global.storageClass.provisioner", "dobs.csi.digitalocean.com") + settings.set("installer-settings.volumeProvisionStrategy", "doksPdDynamic") + prompt = PromptVolumes(settings) + prompt.prompt_app_volume_type() + assert settings.get("installer-settings.volumeProvisionStrategy") == vol_path + + +@pytest.mark.parametrize("vol_choice, vol_path", [ + (26, "localOpenEbsHostPathDynamic"), +]) +def test_prompt_app_volume_choice_local(monkeypatch, settings, vol_choice, vol_path): + from pygluu.kubernetes.terminal.volumes import PromptVolumes + + monkeypatch.setattr("click.prompt", lambda x, default: vol_choice) + + settings.set("global.storageClass.provisioner", "openebs.io/local") + settings.set("installer-settings.volumeProvisionStrategy", "localOpenEbsHostPathDynamic") + prompt = PromptVolumes(settings) + prompt.prompt_app_volume_type() + assert settings.get("installer-settings.volumeProvisionStrategy") == "localOpenEbsHostPathDynamic" + + +@pytest.mark.parametrize("persistence", ["ldap", "hybrid"]) +def test_prompt_storage(monkeypatch, settings, persistence): + from pygluu.kubernetes.terminal.volumes import PromptVolumes + + monkeypatch.setattr("click.prompt", lambda x, default: "4Gi") + + settings.set("global.cnPersistenceType", persistence) + settings.set("opendj.persistence.size", "") + PromptVolumes(settings).prompt_storage() + assert settings.get("opendj.persistence.size") == "4Gi" + + +@pytest.mark.parametrize("persistence", ["ldap", "hybrid"]) +def test_prompt_storage_2(monkeypatch, settings, persistence): + from pygluu.kubernetes.terminal.volumes import PromptVolumes + + monkeypatch.setattr("click.prompt", lambda x, default: "4Gi") + + settings.set("global.cnPersistenceType", persistence) + settings.set("opendj.persistence.size", "5Gi") + PromptVolumes(settings).prompt_storage() + assert settings.get("opendj.persistence.size") == "5Gi" + + +def test_prompt_volumes_microk8s(settings): + from pygluu.kubernetes.terminal.volumes import PromptVolumes + + settings.set("installer-settings.volumeProvisionStrategy", "microk8sDynamic") + PromptVolumes(settings).prompt_volumes() + assert settings.get("global.storageClass.provisioner") == "microk8s.io/hostpath" + + +def test_prompt_volumes_minikube(settings): + from pygluu.kubernetes.terminal.volumes import PromptVolumes + + settings.set("installer-settings.volumeProvisionStrategy", "minikubeDynamic") + PromptVolumes(settings).prompt_volumes() + assert settings.get("global.storageClass.provisioner") == "k8s.io/minikube-hostpath" + + +def test_prompt_volumes_global_azure_type(monkeypatch, settings): + from pygluu.kubernetes.terminal.volumes import PromptVolumes + + monkeypatch.setattr("click.prompt", lambda x, default: "StandardSSD_LRS") + + settings.set("installer-settings.volumeProvisionStrategy", "aksPdDynamic") + settings.set("global.azureStorageAccountType", "") + PromptVolumes(settings).prompt_volumes() + assert settings.get("global.azureStorageAccountType") == "StandardSSD_LRS" + + +def test_prompt_volumes_global_aws_type(monkeypatch, settings): + from pygluu.kubernetes.terminal.volumes import PromptVolumes + + monkeypatch.setattr("click.prompt", lambda x, default: "io1") + + settings.set("installer-settings.volumeProvisionStrategy", "awsEbsDynamic") + settings.set("global.awsStorageType", "") + PromptVolumes(settings).prompt_volumes() + assert settings.get("global.awsStorageType") == "io1" + + +def test_prompt_volumes_global_gke_type(monkeypatch, settings): + from pygluu.kubernetes.terminal.volumes import PromptVolumes + + monkeypatch.setattr("click.prompt", lambda x, default: "pd-ssd") + + settings.set("installer-settings.volumeProvisionStrategy", "gkePdDynamic") + settings.set("global.gcePdStorageType", "") + PromptVolumes(settings).prompt_volumes() + assert settings.get("global.gcePdStorageType") == "pd-ssd" + + +def test_prompt_volumes_global_local_type(monkeypatch, settings): + from pygluu.kubernetes.terminal.volumes import PromptVolumes + + monkeypatch.setattr("click.prompt", lambda x, default: "openebs.io/local") + + settings.set("installer-settings.volumeProvisionStrategy", "localOpenEbsHostPathDynamic") + settings.set("global.storageClass.provisioner", "") + PromptVolumes(settings).prompt_volumes() + assert settings.get("global.storageClass.provisioner") == "openebs.io/local" diff --git a/helm/tests/test_create.py b/helm/tests/test_create.py new file mode 100644 index 00000000000..9557094815c --- /dev/null +++ b/helm/tests/test_create.py @@ -0,0 +1,27 @@ +from pygluu.kubernetes.create import create_parser, main +import pygluu.kubernetes.create as module0 +import argparse +import sys +import pytest + + +def test_empty_arg(): + parser = create_parser() + args = parser.parse_args(['version']) + + assert args is not None + + +def test_main_exception(): + try: + var0 = module0.main() + except BaseException: + pass + + +def test_create_exception(): + try: + var0 = module0.create_parser() + except BaseException: + pass + diff --git a/helm/tests/test_gluu.py b/helm/tests/test_gluu.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/helm/tests/test_gluucouchbase.py b/helm/tests/test_gluucouchbase.py new file mode 100644 index 00000000000..d1b0ffa505b --- /dev/null +++ b/helm/tests/test_gluucouchbase.py @@ -0,0 +1,30 @@ +import pytest +from pygluu.kubernetes.couchbase import set_memory_for_buckets, create_server_spec_per_cb_service, extract_couchbase_tar +from pathlib import Path +import logging + + +def test_create_server_spec_per_cb_service(caplog, tmpdir): + + p = create_server_spec_per_cb_service(zones="zone-1", number_of_cb_service_nodes=2, cb_service_name="couch", mem_req="100Mi", mem_limit="100Mi", + cpu_req="100Mi", cpu_limit="100Mi") + + assert p is p + + +def test_create_server_spec_per_cb_service2(caplog, tmpdir): + + p = create_server_spec_per_cb_service(zones="zone-1", number_of_cb_service_nodes=2, cb_service_name="couch", mem_req="100Mi", mem_limit="100Mi", + cpu_req="100Mi", cpu_limit="100Mi") + + assert p is p + + +def extract_couchbase_tar(caplog, tmpdir): + tar_file = Path(tmpdir) / './couchbase-source-folder' + + extract_couchbase_tar(tar_file) + + + with caplog.at_level(logging.INFO): + assert "Extracting" in caplog.text \ No newline at end of file diff --git a/helm/tests/test_gluuhelpers.py b/helm/tests/test_gluuhelpers.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/helm/tests/test_gluukubeapi.py b/helm/tests/test_gluukubeapi.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/helm/tests/test_gluupostgres.py b/helm/tests/test_gluupostgres.py new file mode 100644 index 00000000000..9ba95d022de --- /dev/null +++ b/helm/tests/test_gluupostgres.py @@ -0,0 +1,9 @@ +import pygluu.kubernetes.postgres as module0 +from pygluu.kubernetes.postgres import Postgres + + +def test_base_exception(): + try: + var0 = module0.Postgres() + except BaseException: + pass diff --git a/helm/tests/test_gluupycert.py b/helm/tests/test_gluupycert.py new file mode 100644 index 00000000000..bbe89191e34 --- /dev/null +++ b/helm/tests/test_gluupycert.py @@ -0,0 +1,36 @@ +import pytest +from pygluu.kubernetes.pycert import setup_crts +from pathlib import Path +import logging + + +def test_setup_certs(tmpdir): + ca_cert_file = Path(tmpdir) / './ca.crt' + ca_key_file = Path(tmpdir) / './ca.key' + cert_file = Path(tmpdir) / './chain.pem' + key_file = Path(tmpdir) / './pkey.key' + + setup_crts(ca_common_name="test", cert_common_name="test", san_list="test", + ca_cert_file=ca_cert_file, + ca_key_file=ca_key_file, + cert_file=cert_file, + key_file=key_file) + + assert True + + +def test_setup_log(caplog, tmpdir): + ca_cert_file = Path(tmpdir) / './ca.crt' + ca_key_file = Path(tmpdir) / './ca.key' + cert_file = Path(tmpdir) / './chain.pem' + key_file = Path(tmpdir) / './pkey.key' + + setup_crts(ca_common_name="test", cert_common_name="test", san_list="test", + ca_cert_file=ca_cert_file, + ca_key_file=ca_key_file, + cert_file=cert_file, + key_file=key_file) + + + with caplog.at_level(logging.INFO): + assert "" in caplog.text \ No newline at end of file diff --git a/helm/tests/test_gluuredis.py b/helm/tests/test_gluuredis.py new file mode 100644 index 00000000000..ee7cc3f7d9d --- /dev/null +++ b/helm/tests/test_gluuredis.py @@ -0,0 +1,9 @@ +import pygluu.kubernetes.redis as module0 +from pygluu.kubernetes.redis import Redis + + +def test_base_exception(): + try: + var0 = module0.Redis() + except BaseException: + pass diff --git a/helm/tests/test_settings.py b/helm/tests/test_settings.py new file mode 100644 index 00000000000..6e89af007fc --- /dev/null +++ b/helm/tests/test_settings.py @@ -0,0 +1,32 @@ +import logging +from pathlib import Path + + +def test_get_exception(caplog, settings): + with caplog.at_level(logging.INFO): + assert settings.get("RANDOM_KEY") is False + assert "No Value" in caplog.text + + +def test_update_exception(caplog, settings): + # set collection to something that is not a collection + collection = 1 + + with caplog.at_level(logging.INFO): + assert settings.update(collection) is False + assert "Uncaught error" in caplog.text + + +def test_reset_data_exception(caplog, monkeypatch, settings): + def fake_store_data(): + 1 / 0 + + monkeypatch.setattr( + "pygluu.kubernetes.settings.ValuesHandler.store_data", + fake_store_data, + ) + + with caplog.at_level(logging.INFO): + assert settings.reset_data() is False + assert "Uncaught error" in caplog.text + diff --git a/helm/tests/test_yamlparser.py b/helm/tests/test_yamlparser.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/helm/tox.ini b/helm/tox.ini new file mode 100644 index 00000000000..25ce023cc58 --- /dev/null +++ b/helm/tox.ini @@ -0,0 +1,17 @@ +[tox] +envlist = py3 +skip_missing_interpreters=true + +[testenv] +deps = + pytest + pytest-cov + pytest-gevent +commands = + pytest-gevent -v --cov-config=.coveragerc --cov=pygluu.kubernetes --cov-report=term-missing:skip-covered --cov-report=xml tests/ + pip install -e . + +[flake8] +# E402: module level import not at top of file +# E501: line too long +ignore = E402,E501