def list_scenario_instance(self, scenario_name_or_path): scenario_name = normalize_scenario_name(scenario_name_or_path) scenario_instance_dir_path = find_scenario_instance_dir( self.base_dir, scenario_name) if scenario_instance_dir_path is None: print( f'[cloudgoat] Error: No scenario instance for "{scenario_name}" found.' f" Try: cloudgoat.py list deployed") return terraform = Terraform( working_dir=os.path.join(scenario_instance_dir_path, "terraform")) show_retcode, show_stdout, show_stderr = terraform.show( capture_output=False, no_color=IsNotFlagged) if show_retcode != 0: display_terraform_step_error("terraform show", show_retcode, show_stdout, show_stderr) return else: print( f"\n[cloudgoat] terraform show completed with no error code.") return
def list_deployed_scenario_instances(self): deployed_scenario_instances = list() for scenario_name in self.scenario_names: scenario_instance_dir_path = find_scenario_instance_dir( self.base_dir, scenario_name) if scenario_instance_dir_path is None: continue else: deployed_scenario_instances.append(scenario_instance_dir_path) if not deployed_scenario_instances: print( f'\n No scenario instance directories exist. Try "list undeployed" or' f' "list all"\n') return else: print( f"\n Deployed scenario instances: {len(deployed_scenario_instances)}" ) for scenario_instance_dir_path in deployed_scenario_instances: directory_name = os.path.basename(scenario_instance_dir_path) scenario_name, cgid = directory_name.split("_cgid") print(f"\n {scenario_name}" f"\n CGID: {'cgid' + cgid}" f"\n Path: {scenario_instance_dir_path}") print("")
def list_all_scenarios(self): undeployed_scenarios = list() deployed_scenario_instance_paths = list() for scenario_name in self.scenario_names: scenario_instance_dir_path = find_scenario_instance_dir( self.base_dir, scenario_name) if scenario_instance_dir_path: deployed_scenario_instance_paths.append( scenario_instance_dir_path) else: undeployed_scenarios.append(scenario_name) print( f"\n Deployed scenario instances: {len(deployed_scenario_instance_paths)}" ) for scenario_instance_dir_path in deployed_scenario_instance_paths: directory_name = os.path.basename(scenario_instance_dir_path) scenario_name, cgid = directory_name.split("_cgid") print(f"\n {scenario_name}" f"\n CGID: {'cgid' + cgid}" f"\n Path: {scenario_instance_dir_path}") print(f"\n Undeployed scenarios: {len(undeployed_scenarios)}") # Visual spacing. if undeployed_scenarios: print(f"") for scenario_name in undeployed_scenarios: print(f" {scenario_name}") print(f"")
def test_find_scenario_instance_dir(self): core.python.utils.dirs_at_location = unittest.mock.Mock(return_value=[ '/tmp/other_scenario_takeover_cgid5o8kwrb5ir', '/tmp/ecs_takeover_cgidkcjqvxvjh8', ]) self.assertEqual( find_scenario_instance_dir('/tmp', 'ecs_takeover'), '/tmp/ecs_takeover_cgidkcjqvxvjh8', ) core.python.utils.dirs_at_location.assert_called_with('/tmp')
def list_undeployed_scenarios(self): undeployed_scenarios = list() for scenario_name in self.scenario_names: if not find_scenario_instance_dir(self.base_dir, scenario_name): undeployed_scenarios.append(scenario_name) if undeployed_scenarios: return print( f"\n Undeployed scenarios: {len(undeployed_scenarios)}\n\n " + f"\n ".join(undeployed_scenarios) + f"\n") else: return print( f'\n All scenarios have been deployed. Try "list deployed" or "list' f' all"\n')
def destroy_scenario(self, scenario_name_or_path, profile, confirmed=False): # Information gathering. scenario_name = normalize_scenario_name(scenario_name_or_path) scenario_instance_dir_path = find_scenario_instance_dir( self.base_dir, scenario_name) if scenario_instance_dir_path is None: print( f'[cloudgoat] Error: No scenario instance for "{scenario_name}" found.' f" Try: cloudgoat.py list deployed") return instance_name = os.path.basename(scenario_instance_dir_path) # Confirmation. if not confirmed: delete_permission = input( f'Destroy "{instance_name}"? [y/n]: ').strip() if not delete_permission or not delete_permission[0].lower( ) == "y": print(f"\nCancelled destruction of {instance_name}.\n") return # Terraform execution. terraform_directory = os.path.join(scenario_instance_dir_path, "terraform") if os.path.exists( os.path.join(terraform_directory, "terraform.tfstate")): terraform = Terraform(working_dir=terraform_directory) cgid = extract_cgid_from_dir_name( os.path.basename(scenario_instance_dir_path)) destroy_retcode, destroy_stdout, destroy_stderr = terraform.destroy( capture_output=False, var={ "cgid": cgid, "cg_whitelist": list(), "profile": profile, "region": self.aws_region, }, no_color=IsNotFlagged, ) if destroy_retcode != 0: display_terraform_step_error("terraform destroy", destroy_retcode, destroy_stdout, destroy_stderr) return else: print( "\n[cloudgoat] terraform destroy completed with no error code." ) else: print( f"\nNo terraform.tfstate file was found in the scenario instance's" f' terraform directory, so "terraform destroy" will not be run.' ) # Scenario instance directory trashing. trash_dir = create_dir_if_nonexistent(self.base_dir, "trash") trashed_instance_path = os.path.join( trash_dir, os.path.basename(scenario_instance_dir_path)) shutil.move(scenario_instance_dir_path, trashed_instance_path) print( f"\nSuccessfully destroyed {instance_name}." f"\nScenario instance files have been moved to {trashed_instance_path}" ) return
def destroy_all_scenarios(self, profile): # Information gathering. extant_scenario_instance_names_and_paths = list() for scenario_name in self.scenario_names: scenario_instance_dir_path = find_scenario_instance_dir( self.base_dir, scenario_name) if scenario_instance_dir_path is None: continue else: extant_scenario_instance_names_and_paths.append( (scenario_name, scenario_instance_dir_path)) print(f"Scenario instance for {scenario_name} found.") if not extant_scenario_instance_names_and_paths: print(f"\n No scenario instance directories exist.\n") return else: print( f"\n {len(extant_scenario_instance_names_and_paths)} scenario" f" instance directories found.") # Iteration. success_count, failure_count, skipped_count = 0, 0, 0 for scenario_name, instance_path in extant_scenario_instance_names_and_paths: print(f"\n--------------------------------\n") # Confirmation. delete_permission = input(f'Destroy "{scenario_name}"? [y/n]: ') if not delete_permission.strip()[0].lower() == "y": skipped_count += 1 print(f"\nSkipped destruction of {scenario_name}.\n") continue # Terraform execution. terraform_directory = os.path.join(instance_path, "terraform") if os.path.exists( os.path.join(terraform_directory, "terraform.tfstate")): terraform = Terraform(working_dir=terraform_directory) cgid = extract_cgid_from_dir_name( os.path.basename(instance_path)) destroy_retcode, destroy_stdout, destroy_stderr = terraform.destroy( capture_output=False, var={ "cgid": cgid, "cg_whitelist": list(), "profile": profile, "region": self.aws_region, }, no_color=IsNotFlagged, ) if destroy_retcode != 0: display_terraform_step_error( "terraform destroy", destroy_retcode, destroy_stdout, destroy_stderr, ) failure_count += 1 # Subsequent destroys should not be skipped when one fails. continue else: print( f"\n[cloudgoat] terraform destroy completed with no error code." ) else: print( f"\nNo terraform.tfstate file was found in the scenario instance's" f' terraform directory, so "terraform destroy" will not be run.' ) # Scenario instance directory trashing. trash_dir = create_dir_if_nonexistent(self.base_dir, "trash") trashed_instance_path = os.path.join( trash_dir, os.path.basename(instance_path)) shutil.move(instance_path, trashed_instance_path) success_count += 1 print( f"\nSuccessfully destroyed {scenario_name}." f"\nScenario instance files have been moved to {trashed_instance_path}" ) # Iteration summary. print(f"\nDestruction complete." f"\n {success_count} scenarios successfully destroyed" f"\n {failure_count} destroys failed" f"\n {skipped_count} skipped\n") return
def create_scenario(self, scenario_name_or_path, profile): scenario_name = normalize_scenario_name(scenario_name_or_path) scenario_dir = os.path.join(self.scenarios_dir, scenario_name) if not scenario_dir or not scenario_name or not os.path.exists( scenario_dir): if not scenario_name: return print( f"No recognized scenario name was entered. Did you mean one of" f" these?\n " + f"\n ".join(self.scenario_names)) else: return print( f"No scenario named {scenario_name} exists in the scenarios" f" directory. Did you mean one of these?" f"\n " + f"\n ".join(self.scenario_names)) if not os.path.exists(self.whitelist_path): cg_whitelist = self.configure_or_check_whitelist(auto=True) else: cg_whitelist = self.configure_or_check_whitelist() if not cg_whitelist: print( f"A valid whitelist.txt file must exist in the {self.base_dir}" f' directory before "create" may be used.') return # Create a scenario-instance folder in the project root directory. # This command should fail with an explanatory error message if a # scenario-instance of the same root name (i.e. without the CGID) already # exists. extant_dir = find_scenario_instance_dir(self.base_dir, scenario_name) if extant_dir is not None: destroy_and_recreate = input( f"You already have an instance of {scenario_name} deployed." f" Do you want to destroy and recreate it (y) or cancel (n)? [y/n]: " ) if destroy_and_recreate.strip().lower() == "y": self.destroy_scenario(scenario_name, profile, confirmed=True) else: instance_name = os.path.basename(extant_dir) print( f"\nCancelled destruction and recreation of {instance_name}.\n" ) return cgid = generate_cgid() scenario_instance_dir_path = os.path.join(self.base_dir, f"{scenario_name}_{cgid}") # Copy all the terraform files from the "/scenarios/scenario-name" folder # to the scenario-instance folder. source_dir_contents = os.path.join(scenario_dir, ".") shutil.copytree(source_dir_contents, scenario_instance_dir_path) if os.path.exists(os.path.join(scenario_instance_dir_path, "start.sh")): print(f"\nNow running {scenario_name}'s start.sh...") start_script_process = subprocess.Popen( ["sh", "start.sh"], cwd=scenario_instance_dir_path) start_script_process.wait() else: pass terraform = Terraform( working_dir=os.path.join(scenario_instance_dir_path, "terraform")) init_retcode, init_stdout, init_stderr = terraform.init( capture_output=False, no_color=IsNotFlagged) if init_retcode != 0: display_terraform_step_error("terraform init", init_retcode, init_stdout, init_stderr) return else: print( f"\n[cloudgoat] terraform init completed with no error code.") plan_retcode, plan_stdout, plan_stderr = terraform.plan( capture_output=False, var={ "cgid": cgid, "cg_whitelist": cg_whitelist, "profile": profile, "region": self.aws_region, }, no_color=IsNotFlagged, ) # For some reason, `python-terraform`'s `terraform init` returns "2" even # when it appears to succeed. For that reason, it will temporarily permit # retcode 2. if plan_retcode not in (0, 2): display_terraform_step_error("terraform plan", plan_retcode, plan_stdout, plan_stderr) return else: print( f"\n[cloudgoat] terraform plan completed with no error code.") apply_retcode, apply_stdout, apply_stderr = terraform.apply( capture_output=False, var={ "cgid": cgid, "cg_whitelist": cg_whitelist, "profile": profile, "region": self.aws_region, }, skip_plan=True, no_color=IsNotFlagged, ) if apply_retcode != 0: display_terraform_step_error("terraform apply", apply_retcode, apply_stdout, apply_stderr) return else: print( f"\n[cloudgoat] terraform apply completed with no error code.") # python-terraform uses the '-json' flag by default. # The documentation for `output` suggests using output_cmd to receive the # library's standard threeple return value. # Can't use capture_output here because we need to write stdout to a file. output_retcode, output_stdout, output_stderr = terraform.output_cmd() if output_retcode != 0: display_terraform_step_error("terraform output", output_retcode, output_stdout, output_stderr) return else: print( f"\n[cloudgoat] terraform output completed with no error code." ) # Within this output will be values that begin with "cloudgoat_output". # Each line of console output which contains this tag will be written into # a text file named "start.txt" in the scenario-instance folder. start_file_path = os.path.join(scenario_instance_dir_path, "start.txt") with open(start_file_path, "w") as start_file: for line in output_stdout.split("\n"): if line.count("cloudgoat_output") != 0: start_file.write(line + "\n") print( f"\n[cloudgoat] Output file written to:\n\n {start_file_path}\n" )