def get_project_config() -> dict: """Load the configuration of the current environment (global configuration)""" project_config_path = os.path.join( Configuration.get_config_path(), Configuration.get_config_projet_filename()) if not io.exists(project_config_path): raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), project_config_path) project_config = io.from_yaml(project_config_path) return _resolve_variables_deep(project_config)
def get_project_config(config_path: str = Configuration.get_config_path() ) -> dict: """Load the global configuration of the project""" project_config_path = os.path.join( config_path, Configuration.get_config_project_filename()) if not io.exists(project_config_path): raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), project_config_path) project_config = yaml_lib.read_yaml(project_config_path) return _resolve_variables_deep(project_config)
def change_environment(environment: str, config_path=Configuration.get_config_path()): """Change the environment (branch) of the configuration""" io.execute("git stash", config_path) io.execute("git fetch origin", config_path) io.execute(f"git checkout {environment}", config_path) io.execute(f"git reset --hard origin/{environment}", config_path)
def advance_workflow(current_step): """Advance the workflow for all applications able to be advanced.""" Logger.info( {"current_step": current_step}, "[/api/workflow/progress/<current_step>] Workflow advance started", ) try: # Creating a copy of the working configuration directory config_dir = config_lib.create_temporary_config_copy() config_lib.change_environment( Configuration.get_config_default_branch(), config_dir) project_config = config_lib.get_project_config(config_dir) report_status, report = workflow_lib.advance_workflow( config_dir, project_config, current_step) status, message = _get_status_and_message(report_status) # Clean up non_blocking_clean( config_dir, message_prefix="[/api/workflow/progress/<current_step>]") # HTTP Response Logger.info( { "current_step": current_step, "report": report }, f"[/api/workflow/progress/<current_step>] {message}", ) return ( { "current_step": current_step, "report": report, "message": message }, status, ) # pylint: disable=broad-except except Exception as err: Logger.error( { "current_step": current_step, "err": str(err) }, "[/api/workflow/progress/<current_step>] Workflow advance failed", ) # HTTP Response return ( { "current_step": current_step, "err": str(err), "message": "Workflow advance failed", }, HTTPStatus.INTERNAL_SERVER_ERROR, )
def get_app_config(app_name: str) -> dict: """Load the configuration of an app""" app_config_path = os.path.join( Configuration.get_config_path(), Configuration.get_config_app_folder(), f"{app_name}.yaml", ) if not io.exists(app_config_path): raise AppConfigurationNotFoundError(app_name) app_config = io.from_yaml(app_config_path) environment_config = get_project_config() config = dict_utils.deep_merge(environment_config, app_config) # Awaiting for implementation # validate configuration using nestor-config-validator return _resolve_variables_deep(config)
def list_apps_config(config_path: str = Configuration.get_config_path() ) -> dict: """Retrieves all of the apps configurations keyed by app names.""" apps_path = os.path.join(config_path, Configuration.get_config_app_folder()) if not os.path.isdir(apps_path): raise ValueError(apps_path) apps_config_hashmap = {} for file_path in os.listdir(apps_path): basename = os.path.basename(file_path) filename = PurePath(basename) file_extension = "".join(filename.suffixes) app_name = filename.name.replace(file_extension, "") # Prevent parsing other files than configuration ones (directories, incorrect extension) if file_extension not in [".yml", ".yaml"]: continue apps_config_hashmap[app_name] = get_app_config(app_name) return apps_config_hashmap
def test_get_config_project_filename_configured(self): self.assertEqual(Configuration.get_config_project_filename(), "custom-name.yaml")
def create_temporary_config_copy() -> str: """Copy the configuration into a temporary directory and returns its path""" return io.create_temporary_copy(Configuration.get_config_path(), "config")
def init_workflow(organization, app): """Initialize the workflow of an application.""" Logger.info( {"app": app, "organization": organization}, "[/api/workflow/init/:org/:app] Workflow initialization started", ) report = None try: # Retrieve project configuration config_dir = config_lib.create_temporary_config_copy() config_lib.change_environment(Configuration.get_config_default_branch(), config_dir) project_config = config_lib.get_project_config(config_dir) git_provider = get_git_provider(project_config) report_status, report = workflow_lib.init_workflow(organization, app, git_provider) if report_status == workflow_lib.WorkflowInitStatus.FAIL: status = HTTPStatus.INTERNAL_SERVER_ERROR message = "Workflow initialization failed" elif report_status == workflow_lib.WorkflowInitStatus.SUCCESS: status = HTTPStatus.OK message = "Workflow initialization succeeded" else: raise ValueError(f"Unexpected status: '{report_status}'") # Clean the temporary directory try: io_lib.remove(config_dir) # pylint: disable=broad-except except Exception as err: Logger.warn( {"config_dir": config_dir, "err": err}, "Failed to clean temporary config dir" ) # HTTP Response Logger.info( {"organization": organization, "app": app, "report": report}, f"[/api/workflow/init/:org/:app] {message}", ) return ( {"organization": organization, "app": app, "report": report, "message": message}, status, ) # pylint: disable=broad-except except Exception as err: Logger.error( {"organization": organization, "app": app, "report": report, "err": str(err)}, "[/api/workflow/init/:org/:app] Workflow initialization failed", ) # HTTP Response return ( { "organization": organization, "app": app, "err": str(err), "message": "Workflow initialization failed", }, HTTPStatus.INTERNAL_SERVER_ERROR, )
def test_get_pristine_path_configured(self): self.assertEqual(Configuration.get_pristine_path(), "/a-custom-pristine-path")
def get_pristine_path(pristine_path_name): """Returns the pristine path""" return os.path.join(Configuration.get_pristine_path(), pristine_path_name)
def test_get_pristine_path_default(self): self.assertEqual(Configuration.get_pristine_path(), "/tmp/nestor/pristine")
def test_get_working_path_configured(monkeypatch): monkeypatch.setenv("NESTOR_WORK_PATH", "/a-custom-working-path") assert Configuration.get_working_path() == "/a-custom-working-path"
def test_get_config_path(monkeypatch): monkeypatch.setenv("NESTOR_CONFIG_PATH", "/a-custom-config-path") assert Configuration.get_config_path() == "/a-custom-config-path"
def test_get_pristine_path_configured(monkeypatch): monkeypatch.setenv("NESTOR_PRISTINE_PATH", "/a-custom-pristine-path") assert Configuration.get_pristine_path() == "/a-custom-pristine-path"
def test_get_config_app_folder_default(self): self.assertEqual(Configuration.get_config_app_folder(), "apps")
def test_get_config_app_folder_configured(self): self.assertEqual(Configuration.get_config_app_folder(), "/a-custom-apps-folder")
def test_get_working_path_default(self): self.assertEqual(Configuration.get_working_path(), "/tmp/nestor/work")
def test_get_working_path_configured(self): self.assertEqual(Configuration.get_working_path(), "/a-custom-working-path")
def test_get_pristine_path_default(): assert Configuration.get_pristine_path() == "/tmp/nestor/pristine"
def test_get_config_project_filename_default(self): self.assertEqual(Configuration.get_config_project_filename(), "project.yaml")
def test_get_working_path_default(): assert Configuration.get_working_path() == "/tmp/nestor/work"
def test_get_config_default_branch_configured(self): self.assertEqual(Configuration.get_config_default_branch(), "master")
def _differed_build(app_name: str): config_dir = None app_dir = None try: # Retrieve app's configuration config_dir = config.create_temporary_config_copy() config.change_environment(Configuration.get_config_default_branch(), config_dir) app_config = config.get_app_config(app_name, config_dir) Logger.debug( {"app": app_name, "config_directory": config_dir}, "[/api/builds/:app] Application's configuration retrieved", ) # Retrieve app's repository app_dir = git.create_working_repository(app_name, app_config["git"]["origin"]) git.branch(app_dir, app_config["workflow"][0]) Logger.debug( {"app": app_name, "working_directory": app_dir}, "[/api/builds/:app] Application's repository retrieved", ) try: # Create a new tag version = app.get_version(app_dir) git_tag = git.tag(app_dir, version) Logger.debug( {"app": app_name, "tag": git_tag}, "[/api/builds/:app] New tag created", ) except Exception as err: # The tag may already exist Logger.warn( {"app": app_name, "err": err}, "[/api/builds/:app] Error while tagging the app" ) # Build and publish the new docker image image_tag = docker.build(app_name, app_dir, app_config) Logger.debug( {"app": app_name, "image": image_tag}, "[/api/builds/:app] Docker image created" ) docker.push(app_name, image_tag, app_config) Logger.debug( {"app": app_name, "image": image_tag}, "[/api/builds/:app] Docker image published on registry", ) # Send the new tag to git git.push(app_dir) Logger.debug({"app": app_name}, "[/api/builds/:app] Tag pushed to Git") except Exception as err: Logger.error( {"app": app_name, "err": err}, "[/api/builds/:app] Error while tagging and building the app", ) # Clean up temporary directories try: if config_dir is not None: io.remove(config_dir) if app_dir is not None: io.remove(app_dir) except Exception as err: Logger.error({"app": app_name, "err": err}, "[/api/builds/:app] Error during cleanup")
def test_get_config_default_branch_default(self): self.assertEqual(Configuration.get_config_default_branch(), "staging")
def get_working_path(working_path_name): """Returns the working path""" return os.path.join(Configuration.get_working_path(), working_path_name)
def test_get_config_path(self): self.assertEqual(Configuration.get_config_path(), "/a-custom-config-path")