diff --git a/ceo/cli/entrypoint.py b/ceo/cli/entrypoint.py index 782cc99..3b825b2 100644 --- a/ceo/cli/entrypoint.py +++ b/ceo/cli/entrypoint.py @@ -8,6 +8,7 @@ from .mysql import mysql from .postgresql import postgresql from .mailman import mailman from .cloud import cloud +from .k8s import k8s @click.group() @@ -23,3 +24,4 @@ cli.add_command(mysql) cli.add_command(postgresql) cli.add_command(mailman) cli.add_command(cloud) +cli.add_command(k8s) diff --git a/ceo/cli/k8s.py b/ceo/cli/k8s.py new file mode 100644 index 0000000..6094e4e --- /dev/null +++ b/ceo/cli/k8s.py @@ -0,0 +1,42 @@ +import os +import traceback + +import click + +from ..utils import http_post +from .utils import handle_sync_response + + +@click.group(short_help='Manage your CSC Kubernetes resources') +def k8s(): + pass + + +@k8s.group(short_help='Manage your CSC Kubernetes account') +def account(): + pass + + +@account.command(short_help='Obtain a kubeconfig') +def activate(): + kubedir = os.path.join(os.environ['HOME'], '.kube') + if not os.path.isdir(kubedir): + os.mkdir(kubedir) + kubeconfig = os.path.join(kubedir, 'config') + resp = http_post('/api/cloud/k8s/accounts/create') + result = handle_sync_response(resp) + try: + if os.path.isfile(kubeconfig): + kubeconfig_bak = os.path.join(kubedir, 'config.bak') + os.rename(kubeconfig, kubeconfig_bak) + with open(kubeconfig, 'w') as fo: + fo.write(result['kubeconfig']) + except Exception: + click.echo(traceback.format_exc()) + click.echo("We weren't able to write the kubeconfig file, so here it is.") + click.echo("Make sure to paste this into your ~/.kube/config.") + click.echo() + click.echo(result['kubeconfig']) + return + click.echo("Congratulations! You have a new kubeconfig in ~/.kube/config.") + click.echo("Run `kubectl cluster-info` to make sure everything is working.") diff --git a/ceod/model/KubernetesService.py b/ceod/model/KubernetesService.py index 7850ded..03d039c 100644 --- a/ceod/model/KubernetesService.py +++ b/ceod/model/KubernetesService.py @@ -25,6 +25,7 @@ class KubernetesService: self.server_url = cfg.get('k8s_server_url') self.jinja_env = jinja2.Environment( loader=jinja2.PackageLoader('ceod.model'), + keep_trailing_newline=True, ) def _run(self, args: List[str], check=True, **kwargs) -> subprocess.CompletedProcess: diff --git a/ceod/model/MailService.py b/ceod/model/MailService.py index 2630497..96facb1 100644 --- a/ceod/model/MailService.py +++ b/ceod/model/MailService.py @@ -35,6 +35,7 @@ class MailService: self.base_domain = cfg.get('base_domain') self.jinja_env = jinja2.Environment( loader=jinja2.PackageLoader('ceod.model'), + keep_trailing_newline=True, ) def send(self, _from: str, to: str, headers: Dict[str, str], content: str): diff --git a/ceod/model/VHostManager.py b/ceod/model/VHostManager.py index 6c25d4c..2c50a21 100644 --- a/ceod/model/VHostManager.py +++ b/ceod/model/VHostManager.py @@ -60,6 +60,7 @@ class VHostManager: self.jinja_env = jinja2.Environment( loader=jinja2.PackageLoader('ceod.model'), + keep_trailing_newline=True, ) rate_limit_secs = cfg.get('cloud vhosts_rate_limit_seconds') diff --git a/ceod/model/templates/kubeconfig.j2 b/ceod/model/templates/kubeconfig.j2 index 98881ef..4c07283 100644 --- a/ceod/model/templates/kubeconfig.j2 +++ b/ceod/model/templates/kubeconfig.j2 @@ -9,8 +9,8 @@ contexts: cluster: kubernetes namespace: {{ namespace }} user: {{ username }} - name: {{ namespace }} -current-context: {{ namespace }} + name: {{ username }} +current-context: {{ username }} kind: Config preferences: {} users: diff --git a/tests/ceo/cli/test_cloud.py b/tests/ceo/cli/test_cloud.py index 73c4fa9..465f1ca 100644 --- a/tests/ceo/cli/test_cloud.py +++ b/tests/ceo/cli/test_cloud.py @@ -1,3 +1,5 @@ +import os + from click.testing import CliRunner from ...utils import gssapi_token_ctx @@ -55,3 +57,22 @@ def test_cloud_vhosts(cli_setup, new_user, cfg): expected = 'Done.\n' assert result.exit_code == 0 assert result.output == expected + + +def test_k8s_account_activate(cli_setup, new_user): + uid = new_user.uid + runner = CliRunner() + old_home = os.environ['HOME'] + os.environ['HOME'] = new_user.home_directory + try: + with gssapi_token_ctx(uid): + result = runner.invoke(cli, ['k8s', 'account', 'activate']) + finally: + os.environ['HOME'] = old_home + expected = ( + "Congratulations! You have a new kubeconfig in ~/.kube/config.\n" + "Run `kubectl cluster-info` to make sure everything is working.\n" + ) + assert result.exit_code == 0 + assert result.output == expected + assert os.path.isfile(os.path.join(new_user.home_directory, '.kube', 'config')) diff --git a/tests/ceod/api/test_cloud.py b/tests/ceod/api/test_cloud.py index 92b274f..e6549e9 100644 --- a/tests/ceod/api/test_cloud.py +++ b/tests/ceod/api/test_cloud.py @@ -203,3 +203,7 @@ def test_k8s_create_account(client, new_user, ldap_conn): assert status == 200 assert data['status'] == 'OK' assert 'kubeconfig' in data + + expire_member(new_user, ldap_conn) + status, _ = client.post('/api/cloud/k8s/accounts/create', principal=uid) + assert status == 403