diff --git a/.github/workflows/kubernetes_test.yaml b/.github/workflows/kubernetes_test.yaml index 14e2ed9333..5ce24fadad 100644 --- a/.github/workflows/kubernetes_test.yaml +++ b/.github/workflows/kubernetes_test.yaml @@ -83,7 +83,7 @@ jobs: run: | mkdir -p local-deployment cd local-deployment - qhub init local --project=thisisatest --domain github-actions.qhub.dev --auth-provider=password + qhub init local --project=thisisatest --domain github-actions.qhub.dev --auth-provider=password --disable-prompt # Need smaller profiles on Minikube sed -i -E 's/(cpu_guarantee):\s+[0-9\.]+/\1: 0.25/g' "qhub-config.yaml" diff --git a/docs/source/dev_guide/minikube.md b/docs/source/dev_guide/minikube.md index fd8a8a0734..ec1b058fd6 100644 --- a/docs/source/dev_guide/minikube.md +++ b/docs/source/dev_guide/minikube.md @@ -199,7 +199,7 @@ cd data ## Initialize configuration Then, initialize the configuration file `qhub-config.yaml` with: ```shell -python -m qhub init local --project=thisisatest --domain github-actions.qhub.dev --auth-provider=password --terraform-state=local +python -m qhub init local --project=thisisatest --domain github-actions.qhub.dev --auth-provider=password --terraform-state=local --disable-prompt ``` ## Generate user password For each user on the `qhub-config.yaml` file needs a password. @@ -436,7 +436,7 @@ mkdir data-test cd data-test export QHUB_GH_BRANCH=main -qhub init local --project=thisisatest --domain github-actions.qhub.dev --auth-provider=password +qhub init local --project=thisisatest --domain github-actions.qhub.dev --auth-provider=password --disable-prompt sed -i -E 's/(cpu_guarantee):\s+[0-9\.]+/\1: 1/g' "qhub-config.yaml" sed -i -E 's/(mem_guarantee):\s+[A-Za-z0-9\.]+/\1: 1G/g' "qhub-config.yaml" diff --git a/docs/source/installation/usage.md b/docs/source/installation/usage.md index d4756cec7f..36b6768dc2 100644 --- a/docs/source/installation/usage.md +++ b/docs/source/installation/usage.md @@ -57,8 +57,10 @@ User authentication will be by `auth0`, and an OAuth2 app will be created on Aut - `--repository`: Repository name that will be used to store the Infrastructure-as-Code on GitHub. - `--repository-auto-provision`: Sets the secrets for the GitHub repository used for CI/CD actions. - `--ssl-cert-email`: Provide an admin's email address so that LetsEncrypt can generate a real SSL certificate for your site. If omitted, the site will use a self-signed cert that may cause problems for some browsers but may be sufficient for testing. +- `--shared-users-group`: If provided, will ensure a `users` group is created and all users will become members. This means a shared folder called `users` will be available to share files with all other users. +- `--disable-prompt`: Don't wait to ask for inputs for missing flags and values. -You will be prompted to enter values for some of the choices above if they are omitted as command line arguments (for example project name and domain). +You will be prompted to enter values for some of the choices above if they are omitted as command line arguments (for example project name and domain, and whether to create a shared users group or not). If you supply the `--disable-prompt` then you will not be asked for inputs - defaults will be used, or the text PLACEHOLDER used in the resulting `qhub-config.yaml` file so you supply necessary values before deploying. The `qhub init` command also generates an initial password for your root Keycloak user: diff --git a/qhub/cli/initialize.py b/qhub/cli/initialize.py index d9b9713a4a..c1662fe181 100644 --- a/qhub/cli/initialize.py +++ b/qhub/cli/initialize.py @@ -61,6 +61,11 @@ def create_init_subcommand(subparser): "--ssl-cert-email", help="Allow generation of a LetsEncrypt SSL cert - requires an administrative email", ) + subparser.add_argument( + "--shared-users-group", + action="store_true", + help="Create a group called `users` so there will be a shared folder for everyone", + ) subparser.set_defaults(func=handle_init) @@ -80,6 +85,7 @@ def handle_init(args): kubernetes_version=args.kubernetes_version, disable_prompt=args.disable_prompt, ssl_cert_email=args.ssl_cert_email, + shared_users_group=args.shared_users_group, ) try: diff --git a/qhub/initialize.py b/qhub/initialize.py index f64dd7e290..4e342fb0ee 100644 --- a/qhub/initialize.py +++ b/qhub/initialize.py @@ -255,6 +255,7 @@ def render_config( kubernetes_version=None, disable_prompt=False, ssl_cert_email=None, + shared_users_group=False, ): config = BASE_CONFIGURATION.copy() config["provider"] = cloud_provider @@ -409,6 +410,18 @@ def render_config( f"Repository to be auto-provisioned is not the full URL of a GitHub repo: {repository}" ) + # Create a default group called `users`? + if not shared_users_group and not disable_prompt: + want_shared_users_group_response = "" + while want_shared_users_group_response.upper() not in ("Y", "N"): + want_shared_users_group_response = input( + "Create a default group called `users` so there is a shared folder for everyone? [Y/N]" + ) + + shared_users_group = want_shared_users_group_response.upper() == "Y" + + config["security"]["shared_users_group"] = shared_users_group + return config diff --git a/qhub/schema.py b/qhub/schema.py index 6485ad6484..a1b138e5eb 100644 --- a/qhub/schema.py +++ b/qhub/schema.py @@ -100,12 +100,6 @@ class TerraformState(Base): config: typing.Optional[typing.Dict[str, str]] -class TerraformModules(Base): - # No longer used, so ignored, but could still be in qhub-config.yaml - repository: str - rev: str - - # ============ Certificate ============= @@ -206,6 +200,7 @@ class Keycloak(Base): class Security(Base): authentication: Authentication keycloak: typing.Optional[Keycloak] + shared_users_group: typing.Optional[bool] = False # ================ Providers =============== @@ -415,9 +410,6 @@ class Main(Base): ci_cd: typing.Optional[CICD] domain: str terraform_state: typing.Optional[TerraformState] - terraform_modules: typing.Optional[ - TerraformModules - ] # No longer used, so ignored, but could still be in qhub-config.yaml certificate: Certificate helm_extensions: typing.Optional[typing.List[HelmExtension]] prefect: typing.Optional[Prefect] diff --git a/qhub/template/{{ cookiecutter.repo_directory }}/infrastructure/kubernetes.tf b/qhub/template/{{ cookiecutter.repo_directory }}/infrastructure/kubernetes.tf index f3b0eb427f..cd840481fc 100644 --- a/qhub/template/{{ cookiecutter.repo_directory }}/infrastructure/kubernetes.tf +++ b/qhub/template/{{ cookiecutter.repo_directory }}/infrastructure/kubernetes.tf @@ -269,6 +269,8 @@ module "kubernetes-keycloak-config" { auth0_subdomain = {{ cookiecutter.security.authentication.config.auth0_subdomain | jsonify }} {%- endif %} + shared_users_group = {{ cookiecutter.security.shared_users_group | default(false) | jsonify }} + depends_on = [ module.kubernetes-keycloak-helm ] diff --git a/qhub/template/{{ cookiecutter.repo_directory }}/infrastructure/modules/kubernetes/keycloak-config/main.tf b/qhub/template/{{ cookiecutter.repo_directory }}/infrastructure/modules/kubernetes/keycloak-config/main.tf index dc43d9c00d..ebbea59524 100644 --- a/qhub/template/{{ cookiecutter.repo_directory }}/infrastructure/modules/kubernetes/keycloak-config/main.tf +++ b/qhub/template/{{ cookiecutter.repo_directory }}/infrastructure/modules/kubernetes/keycloak-config/main.tf @@ -25,6 +25,8 @@ resource "keycloak_group" "admingroup" { } resource "keycloak_group" "usersgroup" { + count = var.shared_users_group ? 1 : 0 + realm_id = keycloak_realm.realm-qhub.id name = "users" @@ -34,10 +36,12 @@ resource "keycloak_group" "usersgroup" { } resource "keycloak_default_groups" "default" { + count = var.shared_users_group ? 1 : 0 + realm_id = keycloak_realm.realm-qhub.id group_ids = [ - keycloak_group.usersgroup.id + keycloak_group.usersgroup[0].id ] } diff --git a/qhub/template/{{ cookiecutter.repo_directory }}/infrastructure/modules/kubernetes/keycloak-config/variables.tf b/qhub/template/{{ cookiecutter.repo_directory }}/infrastructure/modules/kubernetes/keycloak-config/variables.tf index 0f81ce643f..424b4fdd89 100644 --- a/qhub/template/{{ cookiecutter.repo_directory }}/infrastructure/modules/kubernetes/keycloak-config/variables.tf +++ b/qhub/template/{{ cookiecutter.repo_directory }}/infrastructure/modules/kubernetes/keycloak-config/variables.tf @@ -77,3 +77,9 @@ variable "auth0_subdomain" { type = string default = "" } + +variable "shared_users_group" { + description = "Create a default group called users" + type = bool + default = false +} diff --git a/qhub/template/{{ cookiecutter.repo_directory }}/templates/jupyterhub_config.py.j2 b/qhub/template/{{ cookiecutter.repo_directory }}/templates/jupyterhub_config.py.j2 index e5a9799ec5..4cfe0d4035 100644 --- a/qhub/template/{{ cookiecutter.repo_directory }}/templates/jupyterhub_config.py.j2 +++ b/qhub/template/{{ cookiecutter.repo_directory }}/templates/jupyterhub_config.py.j2 @@ -113,15 +113,17 @@ def qhub_configure_profile(user_nss_json, safe_username, profile): profile.setdefault('kubespawner_override', {})['environment'] = preserve_envvars - profile['kubespawner_override']['lifecycle_hooks'] = { - "postStart": { - "exec": { - "command": ["/bin/sh", "-c", ( - "ln -sfn /home/shared /home/jovyan/shared" - )] + if len(groups) > 0: + # Only symlink shared if we have any mounts + profile['kubespawner_override']['lifecycle_hooks'] = { + "postStart": { + "exec": { + "command": ["/bin/sh", "-c", ( + "ln -sfn /home/shared /home/jovyan/shared" + )] + } } } - } # The recursive chown is important when migrating from an # older uid/gid-based NFS, but may be slow for a lot of files. diff --git a/qhub/upgrade.py b/qhub/upgrade.py index c62cf157fe..d592f21826 100644 --- a/qhub/upgrade.py +++ b/qhub/upgrade.py @@ -292,6 +292,9 @@ def _version_specific_upgrade( if "users" in security: del security["users"] if "groups" in security: + if "users" in security["groups"]: + # Ensure the users default group is added to Keycloak + security["shared_users_group"] = True del security["groups"] # Create root password @@ -304,6 +307,11 @@ def _version_specific_upgrade( f"Generated default random password={default_password} for Keycloak root user (Please change at /auth/ URL path).\n" ) + if "terraform_modules" in config: + del config["terraform_modules"] + print( + "Removing terraform_modules field from config as it is no longer used.\n" + ) # project was never needed in Azure - it remained as PLACEHOLDER in earlier qhub inits! if "azure" in config: if "project" in config["azure"]: