Skip to content

Commit

Permalink
Add force flag to create projects and environments (nutanix#307)
Browse files Browse the repository at this point in the history
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
<img width="1188" alt="Screenshot 2023-10-13 at 4 41 27 PM"
src="https://github.com/ideadevice/calm-dsl/assets/52671728/74245941-ab01-4c0d-89f8-e7c56173f783">

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.
<img width="1397" alt="Screenshot 2023-10-13 at 4 55 32 PM"
src="https://github.com/ideadevice/calm-dsl/assets/52671728/8feadd62-bee0-4de3-943f-9f70d2380036">
<img width="1512" alt="Screenshot 2023-10-13 at 5 49 03 PM"
src="https://github.com/ideadevice/calm-dsl/assets/52671728/b691018f-5196-4be8-92f5-4de109330306">

**Readme Preview**
<img width="1243" alt="Screenshot 2023-10-13 at 5 53 11 PM"
src="https://github.com/ideadevice/calm-dsl/assets/52671728/e3056e2b-e241-4a4d-beb8-a74f0b882e2d">

(cherry picked from commit e6b223ce296ba06b43d0fd069f13a417b6040638)
  • Loading branch information
GullapalliAkhil authored and dwivediprab committed Mar 15, 2024
1 parent 0b9576b commit 7c00d90
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 8 deletions.
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <project_file_location>`. 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 <project_file_location> --name <project_name> --description <description>`. Use `no-cache-update` flag to skip cache updations post operation.
- Create project on Calm Server: `calm create project --file <project_file_location> --name <project_name> --description <description>`.\
**Options:**\
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; `--no-cache-update`: flag to skip cache updations post operation.\
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; `--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 <project_name>`. It will print summary of project.
- Update project using dsl file: `calm update project <project_name> --file <project_file_location>`. Environments will not be updated as part of this operation. Use `no-cache-update` flag to skip cache updations post operation.
Expand All @@ -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 <env_file_location> --project <project_name>`. 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 <env_file_location> --project <project_name> --name <environmet_name>`. Use `no-cache-update` flag to skip cache updations post operation.
- Create environment to existing project: `calm create environment --file <env_file_location> --project <project_name> --name <environmet_name>`.\
**Options:**\
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; `--no-cache-update`: flag to skip cache updations post operation.\
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; `--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 <environment_name> --file <env_file_location> --project <project_name>`. Use `no-cache-update` flag to skip cache updations post operation.
- List environments: `calm get environments --project <project_name>`. Get environments of project.
- Delete environment: `calm delete environment <environment_name> --project <project_name>`. Use `no-cache-update` flag to skip cache updations post operation.
Expand Down
11 changes: 9 additions & 2 deletions calm/dsl/cli/environment_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
77 changes: 76 additions & 1 deletion calm/dsl/cli/environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
Expand Down Expand Up @@ -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
11 changes: 9 additions & 2 deletions calm/dsl/cli/project_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
55 changes: 54 additions & 1 deletion calm/dsl/cli/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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

0 comments on commit 7c00d90

Please sign in to comment.