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 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 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 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" )