def switch_to_temp_branch(repo: BlueprintRepo, defined_branch_in_file: str): stashed_flag = False created_remote_flag = False created_local_temp_branch = False random_suffix = "".join( random.choice(string.ascii_lowercase) for i in range(10)) uncommitted_branch_name = UNCOMMITTED_BRANCH_NAME + defined_branch_in_file + "-" + random_suffix stashed_items_before = count_stashed_items(repo) try: if repo.is_dirty() or repo.untracked_files: create_gitkeep_in_branch() stash_local_changes(repo) stashed_flag = True created_local_temp_branch = create_local_temp_branch( repo, uncommitted_branch_name) if stashed_flag: preserve_uncommitted_code(repo) commit_to_local_temp_branch(repo) create_remote_branch(repo, uncommitted_branch_name) created_remote_flag = True except Exception as e: logger.debug(f"An issue while creating temp branch: {str(e)}") if not stashed_flag and (count_stashed_items(repo) > stashed_items_before): revert_from_uncommitted_code(repo) if created_local_temp_branch: revert_from_local_temp_branch(repo, defined_branch_in_file, stashed_flag) delete_temp_local_branch(repo, defined_branch_in_file) if created_remote_flag: delete_temp_remote_branch(repo, defined_branch_in_file) raise return uncommitted_branch_name
def create_temp_branch_and_stash_if_needed(repo: BlueprintRepo, working_branch: str) -> str: temp_working_branch = "" # Checking if: # 1) User has specified not use local (specified a branch) (This func is only called if not specified) # 2) User is in an actual git dir (working_branch) # 3) There is even a need to create a temp branch for out-of-sync reasons: # either repo.is_dirty() (changes have not been committed locally) # or not repo.is_current_branch_synced() (changes committed locally but not pushed to remote) if working_branch and not repo.is_current_state_synced_with_remote(): try: temp_working_branch = switch_to_temp_branch(repo, working_branch) BaseCommand.info( "Using your local blueprint changes (including uncommitted changes and/or untracked files)" ) logger.debug( f"Using temp branch: {temp_working_branch} " f"(This shall include any uncommitted changes and/or untracked files)" ) except Exception as e: logger.error( f"Was not able push your latest changes to temp branch for validation. Reason: {str(e)}" ) return temp_working_branch
def is_tf_blueprint(blueprint_name: str, repo: BlueprintRepo) -> bool: tf_sandbox_flag = False yaml_obj = repo.get_blueprint_yaml(blueprint_name) if "services" in yaml_obj.keys(): if len(yaml_obj["services"]): tf_sandbox_flag = True return tf_sandbox_flag
def is_k8s_blueprint(blueprint_name: str, repo: BlueprintRepo) -> bool: k8s_sandbox_flag = False yaml_obj = repo.get_blueprint_yaml(blueprint_name) for cloud in yaml_obj["clouds"]: if "/" in cloud: k8s_sandbox_flag = True return k8s_sandbox_flag
def debug_output_about_repo_examination(repo: BlueprintRepo, blueprint_name: str): if not repo.repo_has_blueprint(blueprint_name): logger.debug( f"Current repo does not contain a definition for the blueprint '{blueprint_name}'." ) if repo.is_dirty(): logger.debug("You have uncommitted changes") if repo.untracked_files: logger.debug( "Untracked files detected - only staged or committed files will be used when testing local changes" ) if not repo.current_branch_exists_on_remote(): logger.debug("Your current local branch doesn't exist on remote") # raise BadBlueprintRepo("Your current local branch doesn't exist on remote") if not repo.is_current_branch_synced(): logger.debug("Your local branch is not synced with remote")
def figure_out_branches(user_defined_branch: str, blueprint_name: str): temp_working_branch = "" repo = None stashed_flag = False success = True if user_defined_branch: working_branch = user_defined_branch else: # Try to detect branch from current git-enabled folder logger.debug("Branch hasn't been specified. Trying to identify branch from current working directory") try: repo = BlueprintRepo(os.getcwd()) examine_blueprint_working_branch(repo, blueprint_name=blueprint_name) working_branch = get_blueprint_working_branch(repo) BaseCommand.fyi_info(f"Automatically detected current working branch: {working_branch}") except Exception as e: working_branch = None logger.error(f"Branch could not be identified/used from the working directory; reason: {e}.") success = False # Checking if: # 1) User has specified not use local (specified a branch) # 2) User is in an actual git dir (working_branch) # 3) There is even a need to create a temp branch for out-of-sync reasons: # either repo.is_dirty() (changes have not been committed locally) # or not repo.is_current_branch_synced() (changes committed locally but not pushed to remote) if not user_defined_branch and working_branch and not repo.is_current_state_synced_with_remote(): try: temp_working_branch, stashed_flag = switch_to_temp_branch(repo, working_branch) BaseCommand.info( "Using your local blueprint changes (including uncommitted changes and/or untracked files)" ) logger.debug( f"Using temp branch: {temp_working_branch} " f"(This shall include any uncommitted changes and/or untracked files)" ) except Exception as e: logger.error(f"Was not able push your latest changes to temp branch for validation. Reason: {str(e)}") success = False return repo, working_branch, temp_working_branch, stashed_flag, success
def examine_blueprint_working_branch(repo: BlueprintRepo, blueprint_name: str) -> None: if repo.is_repo_detached(): raise BadBlueprintRepo("Repo's HEAD is in detached state") if not repo.repo_has_blueprint(blueprint_name): logger.debug(f"Current repo does not contain a definition for the blueprint '{blueprint_name}'.") if repo.is_dirty(): logger.debug("You have uncommitted changes") if repo.untracked_files: logger.debug( "Untracked files detected - only staged or committed files will be used when testing local changes" ) if not repo.current_branch_exists_on_remote(): logger.debug("Your current local branch doesn't exist on remote") # raise BadBlueprintRepo("Your current local branch doesn't exist on remote") if not repo.is_current_branch_synced(): logger.debug("Your local branch is not synced with remote") return
def get_and_check_folder_based_repo(blueprint_name: str) -> BlueprintRepo: # Try to detect branch from current git-enabled folder logger.debug( "Branch hasn't been specified. Trying to identify branch from current working directory" ) try: repo = BlueprintRepo(os.getcwd()) check_repo_for_errors(repo) debug_output_about_repo_examination(repo, blueprint_name) except Exception as e: logger.error( f"Branch could not be identified/used from the working directory; reason: {e}." ) raise return repo
def setUp(self): self.test_dir = tempfile.mkdtemp() Repo.clone_from(self.git_repo_url, self.test_dir) self.bp_repo = BlueprintRepo(self.test_dir)
class TestBlueprintRepo(unittest.TestCase): @classmethod def setUpClass(cls): super(TestBlueprintRepo, cls).setUpClass() cls.git_repo_url = "https://github.com/QualiSystemsLab/colony-demo-space.git" def setUp(self): self.test_dir = tempfile.mkdtemp() Repo.clone_from(self.git_repo_url, self.test_dir) self.bp_repo = BlueprintRepo(self.test_dir) def test_blueprint_repo_has_blueprints(self): bp_list = self.bp_repo.blueprints self.assertTrue(len(bp_list)) def test_has_non_existing_blueprint(self): fake_bp = "MyTestBp" self.assertFalse(self.bp_repo.repo_has_blueprint(fake_bp)) def test_has_blueprint(self): bp_name = "promotions-manager-all-aws" self.assertTrue(self.bp_repo.repo_has_blueprint(bp_name)) def test_create_repo_from_non_git_dir(self): with tempfile.TemporaryDirectory() as wrong_dir: self.assertRaises(BadBlueprintRepo, BlueprintRepo, wrong_dir) def test_raise_on_bare_repo(self): with tempfile.TemporaryDirectory() as wrong_dir: Repo.init(wrong_dir, bare=True) self.assertRaises(BadBlueprintRepo, BlueprintRepo, wrong_dir) def test_raise_on_repo_without_blueprints_dir(self): with tempfile.TemporaryDirectory() as temp_dir: Repo.init(temp_dir) self.assertRaises(BadBlueprintRepo, BlueprintRepo, temp_dir) def test_raise_on_repo_without_remotes(self): with tempfile.TemporaryDirectory() as temp_dir: Repo.init(temp_dir) os.mkdir(f"{temp_dir}/blueprints") self.assertRaises(BadBlueprintRepo, BlueprintRepo, temp_dir) def test_has_remote_branch(self): self.assertTrue(self.bp_repo.current_branch_exists_on_remote()) def test_no_branch_on_remote(self): local_branch = "my_super_branch" new_branch = self.bp_repo.create_head(local_branch) assert self.bp_repo.active_branch != new_branch new_branch.checkout() self.assertFalse(self.bp_repo.current_branch_exists_on_remote()) def test_is_synced(self): self.assertTrue(self.bp_repo.is_current_branch_synced()) def test_repo_not_synced(self): index = self.bp_repo.index new_file_path = os.path.join(self.test_dir, "new-file-name") open(new_file_path, "w").close() index.add([new_file_path]) author = Actor("An author", "*****@*****.**") committer = Actor("A committer", "*****@*****.**") index.commit("my commit message", author=author, committer=committer) self.assertFalse(self.bp_repo.is_current_branch_synced()) def tearDown(self): # Close the file, the directory will be removed after the test shutil.rmtree(self.test_dir)
def do_start(self): blueprint_name = self.args["<blueprint_name>"] branch = self.args.get("--branch") commit = self.args.get("--commit") name = self.args["--name"] timeout = self.args["--wait"] if timeout is not None: try: timeout = int(timeout) except ValueError: raise DocoptExit("Timeout must be a number") if timeout < 0: raise DocoptExit("Timeout must be positive") try: duration = int(self.args["--duration"] or 120) if duration <= 0: raise DocoptExit("Duration must be positive") except ValueError: raise DocoptExit("Duration must be a number") if commit and branch is None: raise DocoptExit("Since commit is specified, branch is required") inputs = parse_comma_separated_string(self.args["--inputs"]) artifacts = parse_comma_separated_string(self.args["--artifacts"]) repo, working_branch, temp_working_branch, stashed_flag, success = figure_out_branches( branch, blueprint_name) if not success: return self.error("Unable to start Sandbox") # TODO(ddovbii): This obtaining default values magic must be refactored logger.debug( "Trying to obtain default values for artifacts and inputs from local git blueprint repo" ) try: repo = BlueprintRepo(os.getcwd()) if not repo.is_current_branch_synced(): logger.debug( "Skipping obtaining values since local branch is not synced with remote" ) else: for art_name, art_path in repo.get_blueprint_artifacts( blueprint_name).items(): if art_name not in artifacts and art_path is not None: logger.debug( f"Artifact `{art_name}` has been set with default path `{art_path}`" ) artifacts[art_name] = art_path for input_name, input_value in repo.get_blueprint_default_inputs( blueprint_name).items(): if input_name not in inputs and input_value is not None: logger.debug( f"Parameter `{input_name}` has been set with default value `{input_value}`" ) inputs[input_name] = input_value except Exception as e: logger.debug(f"Unable to obtain default values. Details: {e}") branch_to_be_used = temp_working_branch or working_branch if name is None: suffix = datetime.datetime.now().strftime("%b%d-%H:%M:%S") branch_name_or_type = "" if working_branch: branch_name_or_type = working_branch + "-" if temp_working_branch: branch_name_or_type = "localchanges-" name = f"{blueprint_name}-{branch_name_or_type}{suffix}" try: sandbox_id = self.manager.start(name, blueprint_name, duration, branch_to_be_used, commit, artifacts, inputs) BaseCommand.action_announcement("Starting sandbox") BaseCommand.important_value("Id: ", sandbox_id) BaseCommand.url( prefix_message="URL: ", message=self.manager.get_sandbox_ui_link(sandbox_id)) except Exception as e: logger.exception(e, exc_info=False) sandbox_id = None if temp_working_branch.startswith(UNCOMMITTED_BRANCH_NAME): revert_from_temp_branch(repo, working_branch, stashed_flag) delete_temp_branch(repo, temp_working_branch) return self.die() # todo: I think the below can be simplified and refactored if timeout is None: revert_wait_and_delete_temp_branch(self.manager, blueprint_name, repo, sandbox_id, stashed_flag, temp_working_branch, working_branch) return self.success("The Sandbox was created") else: start_time = datetime.datetime.now() logger.debug(f"Waiting for the Sandbox {sandbox_id} to start...") # Waiting loop while (datetime.datetime.now() - start_time).seconds < timeout * 60: sandbox = self.manager.get(sandbox_id) status = getattr(sandbox, "sandbox_status") if status == "Active": revert_and_delete_temp_branch(repo, working_branch, temp_working_branch, stashed_flag) return self.success(sandbox_id) elif status == "Launching": progress = getattr(sandbox, "launching_progress") for check_points, properties in progress.items(): logger.debug(f"{check_points}: {properties['status']}") time.sleep(30) else: revert_and_delete_temp_branch(repo, working_branch, temp_working_branch, stashed_flag) return self.die( f"The Sandbox {sandbox_id} has started. Current state is: {status}" ) # timeout exceeded logger.error( f"Sandbox {sandbox_id} was not active after the provided timeout of {timeout} minutes" ) revert_and_delete_temp_branch(repo, working_branch, temp_working_branch, stashed_flag) return self.die()
def check_repo_for_errors(repo: BlueprintRepo) -> None: if repo.is_repo_detached(): logger.error("Repo's HEAD is in detached state") raise BadBlueprintRepo("Repo's HEAD is in detached state")
def delete_temp_local_branch(repo: BlueprintRepo, temp_branch: str) -> None: logger.debug(f"[GIT] Deleting local branch {temp_branch}") repo.delete_head("-D", temp_branch)