From 7c00d90ded70e52ad96afacfe3e611a600a18a10 Mon Sep 17 00:00:00 2001 From: Gullapalli Akhil Sai Date: Tue, 17 Oct 2023 15:17:10 +0530 Subject: [PATCH] Add force flag to create projects and environments (#307) If entities associated with project/environment, force create is not possible. So in this case showing the usage of projects/environments like shown in below screen Screenshot 2023-10-13 at 4 41 27 PM If there are no entities associated with project/environment and if the project/environment is existed in system we try to delete them and create a new one. Screenshot 2023-10-13 at 4 55 32 PM Screenshot 2023-10-13 at 5 49 03 PM **Readme Preview** Screenshot 2023-10-13 at 5 53 11 PM (cherry picked from commit e6b223ce296ba06b43d0fd069f13a417b6040638) --- README.md | 11 +++- calm/dsl/cli/environment_commands.py | 11 +++- calm/dsl/cli/environments.py | 77 +++++++++++++++++++++++++++- calm/dsl/cli/project_commands.py | 11 +++- calm/dsl/cli/projects.py | 55 +++++++++++++++++++- 5 files changed, 157 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f9762eae..51192483 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,11 @@ Use `calm get roles` to list all roles in PC. The below roles are relevant for C ### Projects - Compile project: `calm compile project --file `. This command will print the compiled project JSON. Look at sample file [here](examples/Project/demo_project.py) and [here](examples/Project/project_with_env.py). -- Create project on Calm Server: `calm create project --file --name --description `. Use `no-cache-update` flag to skip cache updations post operation. +- Create project on Calm Server: `calm create project --file --name --description `.\ +**Options:**\ +           `--no-cache-update`: flag to skip cache updations post operation.\ +           `--force`: flag to delete existing project with the same name before create, if entities are not associated with it. + - List projects: `calm get projects`. Get projects, optionally filtered by a string - Describe project: `calm describe project `. It will print summary of project. - Update project using dsl file: `calm update project --file `. Environments will not be updated as part of this operation. Use `no-cache-update` flag to skip cache updations post operation. @@ -165,7 +169,10 @@ Use `calm get roles` to list all roles in PC. The below roles are relevant for C ### Environments - Compile environment: `calm compile environment --file --project `. Command will print the compiled environment JSON. Look at sample file [here](examples/Environment/sample_environment.py) -- Create environment to existing project: `calm create environment --file --project --name `. Use `no-cache-update` flag to skip cache updations post operation. +- Create environment to existing project: `calm create environment --file --project --name `.\ +**Options:**\ +           `--no-cache-update`: flag to skip cache updations post operation.\ +           `--force`: flag to delete existing environment in a project with the same name before create, if entities are not associated with it. - Update environment: `calm update environment --file --project `. Use `no-cache-update` flag to skip cache updations post operation. - List environments: `calm get environments --project `. Get environments of project. - Delete environment: `calm delete environment --project `. Use `no-cache-update` flag to skip cache updations post operation. diff --git a/calm/dsl/cli/environment_commands.py b/calm/dsl/cli/environment_commands.py index d3955d52..6c6ed614 100644 --- a/calm/dsl/cli/environment_commands.py +++ b/calm/dsl/cli/environment_commands.py @@ -81,14 +81,21 @@ def _delete_environment(environment_name, project_name, no_cache_update): default=False, help="if true, cache is not updated for project", ) -def _create_environment(env_file, env_name, project_name, no_cache_update): +@click.option( + "--force", + "-fc", + is_flag=True, + default=False, + help="Deletes existing environment with the same name before create, if entities are not associated with it.", +) +def _create_environment(env_file, env_name, project_name, no_cache_update, force): """ Creates a environment to existing project. """ if env_file.endswith(".py"): create_environment_from_dsl_file( - env_file, env_name, project_name, no_cache_update + env_file, env_name, project_name, no_cache_update, force ) else: LOG.error("Unknown file format {}".format(env_file)) diff --git a/calm/dsl/cli/environments.py b/calm/dsl/cli/environments.py index 66a7466f..465a02ff 100644 --- a/calm/dsl/cli/environments.py +++ b/calm/dsl/cli/environments.py @@ -206,7 +206,7 @@ def get_env_class_from_module(user_env_module): def create_environment_from_dsl_file( - env_file, env_name, project_name, no_cache_update=False + env_file, env_name, project_name, no_cache_update=False, force=False ): """ Helper creates an environment from dsl file (for calm_version >= 3.2) @@ -217,6 +217,31 @@ def create_environment_from_dsl_file( Returns: response (object): Response object containing environment object details """ + if force: + env_exist, res = is_environment_exist(env_name, project_name) + if env_exist: + entities = get_environments_usage(res["metadata"]["uuid"], project_name) + if entities: + click.echo(highlight_text("\n-------- Environments usage --------\n")) + for entity in entities: + click.echo( + highlight_text(list(entity.keys())[0]) + + ": " + + highlight_text(list(entity.values())[0]) + ) + LOG.error( + f"\nEnvironment with name {env_name} has entities associated with it, environment creation with same name cannot be forced.\n" + ) + sys.exit(-1) + else: + LOG.info( + f"Forcing the environment create with name {env_name} by deleting the existing environment with same name" + ) + delete_environment(env_name, project_name) + else: + LOG.info( + f"Environment with same name {env_name} does not exist in system, no need of forcing the environment create" + ) # Update project on context ContextObj = get_context() @@ -509,3 +534,53 @@ def delete_environment(environment_name, project_name, no_cache_update=False): LOG.info("Updating environments cache ...") Cache.delete_one(entity_type=CACHE.ENTITY.ENVIRONMENT, uuid=environment_id) LOG.info("[Done]") + + +def is_environment_exist(env_name, project_name): + client = get_api_client() + payload = { + "length": 250, + "offset": 0, + "filter": "name=={}".format(env_name), + } + + if project_name: + project = get_project(project_name) + project_id = project["metadata"]["uuid"] + payload["filter"] += ";project_reference=={}".format(project_id) + + res, err = client.environment.list(payload) + if err: + raise Exception("[{}] - {}".format(err["code"], err["error"])) + + res = res.json() + if res["metadata"]["total_matches"] == 0: + return False, None + + return True, res["entities"][0] + + +def get_environments_usage(env_uuid, project_name): + filter = {"filter": {"environment_reference_list": [env_uuid]}} + client = get_api_client() + project_name_uuid_map = client.project.get_name_uuid_map() + project_id = project_name_uuid_map.get(project_name) + res, err = client.project.usage(project_id, filter) + if err: + LOG.error(err) + sys.exit(-1) + + entities = [] + + def collect_entities(usage): + for entity_name, count in usage.items(): + if entity_name not in ["environment", "marketplace_item"]: + if isinstance(count, dict): + collect_entities(count) + continue + if count > 0: + entities.append({entity_name: count}) + + res = res.json() + collect_entities(res["status"]["usage"]) + return entities diff --git a/calm/dsl/cli/project_commands.py b/calm/dsl/cli/project_commands.py index 7553a515..ae5696de 100644 --- a/calm/dsl/cli/project_commands.py +++ b/calm/dsl/cli/project_commands.py @@ -86,12 +86,19 @@ def _compile_project_command(project_file, out): default=False, help="if true, cache is not updated for project", ) -def _create_project(project_file, project_name, description, no_cache_update): +@click.option( + "--force", + "-fc", + is_flag=True, + default=False, + help="Deletes existing project with the same name before create, if entities are not associated with it.", +) +def _create_project(project_file, project_name, description, no_cache_update, force): """Creates a project""" if project_file.endswith(".py"): create_project_from_dsl( - project_file, project_name, description, no_cache_update + project_file, project_name, description, no_cache_update, force ) else: LOG.error("Unknown file format") diff --git a/calm/dsl/cli/projects.py b/calm/dsl/cli/projects.py index 1dfe0404..78d698e9 100644 --- a/calm/dsl/cli/projects.py +++ b/calm/dsl/cli/projects.py @@ -391,7 +391,7 @@ def update_project(project_uuid, project_payload): def create_project_from_dsl( - project_file, project_name, description="", no_cache_update=False + project_file, project_name, description="", no_cache_update=False, force=False ): """Steps: 1. Creation of project without env @@ -400,6 +400,34 @@ def create_project_from_dsl( """ client = get_api_client() + if force: + project_name_uuid_map = client.project.get_name_uuid_map() + project_id = project_name_uuid_map.get(project_name) + if project_id: + entities = get_projects_usage(project_name) + if entities: + click.echo(highlight_text("\n-------- Projects usage --------\n")) + for entity in entities: + click.echo( + highlight_text(list(entity.keys())[0]) + + ": " + + highlight_text(list(entity.values())[0]) + ) + click.echo( + highlight_text( + f"\nProject with name {project_name} has entities associated with it, project creation with same name cannot be forced.\n" + ) + ) + sys.exit(-1) + else: + LOG.info( + f"Forcing the project create with name {project_name} by deleting the existing project with same name" + ) + delete_project([project_name]) + else: + LOG.info( + f"Project with same name {project_name} does not exist in system, no need of forcing the project create" + ) user_project_module = get_project_module_from_file(project_file) UserProject = get_project_class_from_module(user_project_module) @@ -1503,3 +1531,28 @@ def get_project_usage_payload(project_payload, old_project_payload): } return project_usage_payload + + +def get_projects_usage(project_name, filter={"filter": {}}): + client = get_api_client() + project_name_uuid_map = client.project.get_name_uuid_map() + project_id = project_name_uuid_map.get(project_name) + res, err = client.project.usage(project_id, filter) + if err: + LOG.error(err) + sys.exit(-1) + + entities = [] + + def collect_entities(usage): + for entity_name, count in usage.items(): + if entity_name not in ["environment", "marketplace_item"]: + if isinstance(count, dict): + collect_entities(count) + continue + if count > 0: + entities.append({entity_name: count}) + + res = res.json() + collect_entities(res["status"]["usage"]) + return entities