Skip to content

Commit c040d61

Browse files
CFE-3948: Changed remove to either remove all dependencies, else nothing
Ticket: CFE-3948 Signed-off-by: Simon Halvorsen <simon.halvorsen@northern.tech>
1 parent 8b269de commit c040d61

1 file changed

Lines changed: 111 additions & 57 deletions

File tree

cfbs/commands.py

Lines changed: 111 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ def search_command(terms: List[str]):
5050
import copy
5151
import logging as log
5252
import json
53-
import functools
5453
from typing import Callable, List, Optional, Union
5554
from collections import OrderedDict
5655
from cfbs.analyze import analyze_policyset
@@ -416,89 +415,141 @@ def remove_command(to_remove: List[str]):
416415
modules = config["build"]
417416

418417
def _get_dependents(dependency) -> list:
418+
"""Get all modules that depend on the given module"""
419419
if len(modules) < 2:
420420
return []
421-
422-
def reduce_dependencies(a, b):
423-
result_b = [b["name"]] if dependency in b.get("dependencies", []) else []
424-
if type(a) is list:
425-
return a + result_b
426-
else:
427-
return (
428-
[a["name"]] if dependency in a.get("dependencies", []) else []
429-
) + result_b
430-
431-
return functools.reduce(reduce_dependencies, modules)
421+
dependents = []
422+
for module in modules:
423+
if dependency in module.get("dependencies", []):
424+
dependents.append(module["name"])
425+
return dependents
426+
427+
def _get_all_dependents_recursive(module_name, visited=None) -> set:
428+
"""Recursively get all modules that depend on this module"""
429+
if visited is None:
430+
visited = set()
431+
if module_name in visited:
432+
return visited
433+
visited.add(module_name)
434+
435+
direct_dependents = _get_dependents(module_name)
436+
for dependent in direct_dependents:
437+
_get_all_dependents_recursive(dependent, visited)
438+
return visited
432439

433440
def _get_module_by_name(name) -> Union[dict, None]:
434441
if not name.startswith("./") and name.endswith(".cf") and os.path.exists(name):
435442
name = "./" + name
436-
437443
for module in modules:
438444
if module["name"] == name:
439445
return module
440446
return None
441447

442-
def _remove_module_user_prompt(module):
443-
dependents = _get_dependents(module["name"])
444-
return prompt_user_yesno(
445-
config.non_interactive,
446-
"Do you wish to remove '%s'?" % module["name"]
447-
+ (
448-
" (The module is a dependency of the following module%s: %s)"
449-
% ("s" if len(dependents) > 1 else "", ", ".join(dependents))
450-
if dependents
451-
else ""
452-
),
453-
)
454-
455448
def _get_modules_by_url(name) -> list:
456449
r = []
457450
for module in modules:
458451
if "url" in module and module["url"] == name:
459452
r.append(module)
460453
return r
461454

462-
num_removed = 0
463-
msg = ""
464-
files = []
455+
modules_to_remove = set()
456+
465457
for name in to_remove:
466458
if name.startswith(SUPPORTED_URI_SCHEMES):
467459
matches = _get_modules_by_url(name)
468460
if not matches:
469461
raise CFBSExitError("Could not find module with URL '%s'" % name)
470462
for module in matches:
471-
if _remove_module_user_prompt(module):
472-
print("Removing module '%s'" % module["name"])
473-
modules.remove(module)
474-
msg += "\n - Removed module '%s'" % module["name"]
475-
num_removed += 1
463+
all_affected = _get_all_dependents_recursive(module["name"])
464+
all_affected.discard(module["name"])
465+
if all_affected:
466+
prompt_msg = (
467+
"Module '%s' is a dependency of the following module%s: %s.\n"
468+
"Removing '%s' will also remove %s.\n"
469+
"Do you wish to continue?"
470+
) % (
471+
module["name"],
472+
"s" if len(all_affected) > 1 else "",
473+
", ".join(sorted(all_affected)),
474+
module["name"],
475+
"these modules" if len(all_affected) > 1 else "this module",
476+
)
477+
else:
478+
prompt_msg = "Do you wish to remove '%s'?" % module["name"]
479+
480+
if prompt_user_yesno(
481+
config.non_interactive,
482+
prompt_msg,
483+
):
484+
modules_to_remove.add(module["name"])
485+
modules_to_remove.update(all_affected)
486+
else:
487+
print("Skipping removal of '%s'" % module["name"])
476488
else:
477489
module = _get_module_by_name(name)
478-
if module:
479-
if _remove_module_user_prompt(module):
480-
print("Removing module '%s'" % name)
481-
modules.remove(module)
482-
msg += "\n - Removed module '%s'" % module["name"]
483-
num_removed += 1
484-
else:
490+
if not module:
485491
print("Module '%s' not found" % name)
486-
input_path = os.path.join(".", name, "input.json")
487-
if os.path.isfile(input_path) and prompt_user_yesno(
488-
config.non_interactive,
489-
"Module '%s' has input data '%s'. Do you want to remove it?"
490-
% (name, input_path),
491-
default="no",
492-
):
493-
rm(input_path)
494-
files.append(input_path)
495-
msg += "\n - Removed input data for module '%s'" % name
496-
log.debug("Deleted module data '%s'" % input_path)
492+
continue
497493

498-
num_lines = len(msg.strip().splitlines())
499-
changes_made = num_lines > 0
500-
deps = get_unused_dependancies(config)
494+
all_affected = _get_all_dependents_recursive(module["name"])
495+
all_affected.discard(module["name"])
496+
if all_affected:
497+
prompt_msg = (
498+
"Module '%s' is a dependency of the following module%s: %s.\n"
499+
"Removing '%s' will also remove %s.\n"
500+
"Do you wish to continue?"
501+
) % (
502+
module["name"],
503+
"s" if len(all_affected) > 1 else "",
504+
", ".join(sorted(all_affected)),
505+
module["name"],
506+
"these modules" if len(all_affected) > 1 else "this module",
507+
)
508+
else:
509+
prompt_msg = "Do you wish to remove '%s'?" % module["name"]
510+
511+
if prompt_user_yesno(
512+
config.non_interactive,
513+
prompt_msg,
514+
):
515+
modules_to_remove.add(module["name"])
516+
modules_to_remove.update(all_affected)
517+
else:
518+
print("Skipping removal of '%s'" % module["name"])
501519

520+
removed_modules = []
521+
msg = ""
522+
files = []
523+
524+
while modules_to_remove:
525+
for module_name in modules_to_remove:
526+
dependents_in_set = [
527+
d for d in _get_dependents(module_name) if d in modules_to_remove
528+
]
529+
if not dependents_in_set:
530+
module = _get_module_by_name(module_name)
531+
if module:
532+
print("Removing module '%s'" % module_name)
533+
modules.remove(module)
534+
removed_modules.append(module)
535+
msg += "\n - Removed module '%s'" % module_name
536+
modules_to_remove.remove(module_name)
537+
538+
input_path = os.path.join(".", module_name, "input.json")
539+
if os.path.isfile(input_path) and prompt_user_yesno(
540+
config.non_interactive,
541+
"Module '%s' has input data '%s'. Do you want to remove it?"
542+
% (module_name, input_path),
543+
default="no",
544+
):
545+
rm(input_path)
546+
files.append(input_path)
547+
msg += "\n - Removed input data for module '%s'" % module_name
548+
log.debug("Deleted module data '%s'" % input_path)
549+
break
550+
551+
# Remove orphans
552+
deps = get_unused_dependancies(config)
502553
if deps:
503554
print(
504555
"The following modules were added as dependencies but are no longer needed:"
@@ -513,11 +564,14 @@ def _get_modules_by_url(name) -> list:
513564
config.non_interactive,
514565
"Do you wish to remove these modules?",
515566
):
516-
num_lines += len(deps)
517567
for dep in sorted(deps, key=lambda x: x["name"]):
518568
msg += "\n - Removed module '%s'" % dep["name"]
519-
num_removed += 1
520569
modules.remove(dep)
570+
removed_modules.append(dep)
571+
572+
num_removed = len(removed_modules)
573+
num_lines = len(msg.strip().splitlines())
574+
changes_made = num_lines > 0
521575

522576
if num_lines > 1:
523577
msg = "Removed %d modules\n" % num_removed + msg

0 commit comments

Comments
 (0)