diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 9155d76909c906b3c35ab166d208aa2cb804ef20..091b9fafe2a468398432e88b6aca4e6f2df0846f 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -58,7 +58,8 @@ these applications form the script manually. # Execute the upgrade preparation script ./rename-to-stackspin.sh -After this, you need to manually re-install the applications by running +After this, you need to update secrets and Flux in the cluster by running +``install/install-stackspin.sh``. Then re-install applications by running ``install/install-app.sh <app>`` from the Stackspin repository. See the application specific upgrade guides below. @@ -75,12 +76,17 @@ Nextcloud ~~~~~~~~~ Your SSO users will have new usernames, because the OIDC provider has been -renamed from ``oas`` to ``stackspin``. You can choose from these options: +renamed from ``oas`` to ``stackspin`` and because the new SSO system uses UUIDs +to uniquely identify users. + +You can choose from these options: 1. Manually re-upload and re-share your files after logging into your new user for the first time. 2. It is possible to transfer files from your previous user to the new user. To - do so: + do so, find your new username. It is visible in Settings -> Sharing behind + "Your Federated Cloud ID" after you've logged out and in to Nextcloud with + the new SSO (the part *before* the ``@``). .. code:: bash @@ -89,7 +95,7 @@ renamed from ``oas`` to ``stackspin``. You can choose from these options: # Change to the www-data user su www-data -s /bin/bash # Repeat this command for each username - php occ files:transfer-ownership oas-<username> stackspin-<username> + php occ files:transfer-ownership oas-<old username> <new user ID> # Note: the files are tranferred to a subfolder in the new user's # directory diff --git a/install/generate_secrets.py b/install/generate_secrets.py index bf6cf0e013fa225ba6afb3c40e1690c64c8b33b9..56666eaf24c2f2e7f869df1a1efaef13888f50ad 100644 --- a/install/generate_secrets.py +++ b/install/generate_secrets.py @@ -56,16 +56,30 @@ def create_variables_secret(app_name, env): with open(variables_filename) as template_file: lines = template_file.read() secret_name, secret_namespace = get_secret_metadata(lines) - if get_kubernetes_secret(secret_name, secret_namespace) is None: - print(f"Adding secret {secret_name} in namespace" - f" {secret_namespace} to cluster.") - template = env.from_string(lines) - store_kubernetes_secret(template.render(), secret_namespace) + new_secret_dict = yaml.safe_load(env.from_string(lines).render()) + current_secret_data = get_kubernetes_secret_data(secret_name, + secret_namespace) + if current_secret_data is None: + # Create new secret + update_secret = False + elif current_secret_data.keys() != new_secret_dict['data'].keys(): + # Update current secret with new keys + update_secret = True + print(f"Secret {secret_name} in namespace {secret_namespace}" + " already exists. Merging...") + # Merge dicts. Values from current_secret_data take precedence + new_secret_dict['data'] |= current_secret_data else: + # Do Nothing print(f"Secret {secret_name} in namespace {secret_namespace}" - " already exists. Not generating new secrets.") + " is already in a good state, doing nothing.") + return + print(f"Storing secret {secret_name} in namespace" + f" {secret_namespace} in cluster.") + store_kubernetes_secret(new_secret_dict, secret_namespace, + update=update_secret) else: - print(f'File {variables_filename} does not exist.') + print(f'File {variables_filename} does not exist, no action needed') def create_basic_auth_secret(app_name, env): @@ -77,7 +91,7 @@ def create_basic_auth_secret(app_name, env): lines = template_file.read() secret_name, secret_namespace = get_secret_metadata(lines) - if get_kubernetes_secret(secret_name, secret_namespace) is None: + if get_kubernetes_secret_data(secret_name, secret_namespace) is None: basic_auth_username = 'admin' basic_auth_password = generate_password(32) basic_auth_htpasswd = gen_htpasswd( @@ -91,27 +105,28 @@ def create_basic_auth_secret(app_name, env): 'pass': basic_auth_password, 'htpasswd': basic_auth_htpasswd }) - store_kubernetes_secret(template.render(), secret_namespace) + secret_dict = yaml.safe_load(template.render()) + store_kubernetes_secret(secret_dict, secret_namespace) else: print(f"Secret {secret_name} in namespace {secret_namespace}" " already exists. Not generating new secrets.") else: - print(f'File {basic_auth_filename} does not exist.') + print(f'File {basic_auth_filename} does not exist, no action needed') def get_secret_metadata(yaml_string): """Returns secret name and namespace from metadata field in a yaml string""" - secret_info = yaml.safe_load(yaml_string) - secret_name = secret_info['metadata']['name'] + secret_dict = yaml.safe_load(yaml_string) + secret_name = secret_dict['metadata']['name'] # default namespace is flux-system, but other namespace can be # provided in secret metadata - if 'namespace' in secret_info['metadata']: - secret_namespace = secret_info['metadata']['namespace'] + if 'namespace' in secret_dict['metadata']: + secret_namespace = secret_dict['metadata']['namespace'] else: secret_namespace = 'flux-system' return secret_name, secret_namespace -def get_kubernetes_secret(secret_name, namespace): +def get_kubernetes_secret_data(secret_name, namespace): """Returns the contents of a kubernetes secret or None if the secret does not exist.""" try: secret = API.read_namespaced_secret(secret_name, namespace).data @@ -122,15 +137,28 @@ def get_kubernetes_secret(secret_name, namespace): return None return secret -def store_kubernetes_secret(secret_string, namespace): - """Converts secret_string into a yaml object and adds it to the cluster""" - secret_yaml = yaml.safe_load(secret_string) +def store_kubernetes_secret(secret_dict, namespace, update=False): + """Stores either a new secret in the cluster, or updates an existing one""" + api_client = client.api_client.ApiClient() + if update: + verb = "updated" + api_response = patch_kubernetes_secret(secret_dict, namespace) + else: + verb = "created" + api_response = create_from_yaml( + api_client, + yaml_objects=[secret_dict], + namespace=namespace) + print(f"Secret {verb} with api response: {api_response}") + +def patch_kubernetes_secret(secret_dict, namespace): + """Patches secret in the cluster with new data""" api_client = client.api_client.ApiClient() - api_response = create_from_yaml( - api_client, - yaml_objects=[secret_yaml], - namespace=namespace) - print(f"Secret created with api response: {api_response}") + api_instance = client.CoreV1Api(api_client) + name = secret_dict['metadata']['name'] + body = {} + body['data'] = secret_dict['data'] + return api_instance.patch_namespaced_secret(name, namespace, body) def generate_password(length): """Generates a password of "length" characters"""