def test_git(capfd: Capture, faker: Faker) -> None: create_project( capfd=capfd, name=random_project_name(faker), ) init_project(capfd) assert git.get_repo("does/not/exist") is None do_repo = git.get_repo("submodules/do") assert do_repo is not None assert git.get_active_branch(None) is None assert git.get_active_branch(do_repo) == __version__ assert not git.switch_branch(None, branch_name="0.7.3") # Same branch => no change => return True assert git.switch_branch(do_repo, branch_name=__version__) assert not git.switch_branch(do_repo, branch_name="XYZ") assert git.switch_branch(do_repo, branch_name="0.7.3") assert git.get_active_branch(do_repo) == "0.7.3" assert git.switch_branch(do_repo, branch_name=__version__) assert git.get_active_branch(do_repo) == __version__ assert git.get_origin(None) is None r = git.get_repo(".") assert git.get_origin(r) == "https://your_remote_git/your_project.git" # Create an invalid repo (i.e. without any remote) r = git.init("../justatest") assert git.get_origin(r) is None
def git_submodules(from_path: Optional[Path] = None) -> None: """Check and/or clone git projects""" submodules = (Configuration.specs.get("variables", {}).get("submodules", {}).copy()) main_repo = git.get_repo(".") # This is to reassure mypy, but this is check is already done # in preliminary checks, so it can never happen if not main_repo: # pragma: no cover print_and_exit("Current folder is not a git main_repository") Application.gits["main"] = main_repo for name, submodule in submodules.items(): repo = Application.working_clone(name, submodule, from_path=from_path) if repo: Application.gits[name] = repo
def inspect_main_folder(self, folder: Path) -> Optional[str]: """ RAPyDo commands only works on rapydo projects, we want to ensure that the current folder have a rapydo-like structure. These checks are based on file existence. Further checks are performed in the following steps """ r = git.get_repo(str(folder)) if r is None or git.get_origin(r) is None: return f"""You are not in a git repository \nPlease note that this command only works from inside a rapydo-like repository Verify that you are in the right folder, now you are in: {Path.cwd()} """ for fpath in self.expected_main_folders: if not folder.joinpath(fpath).is_dir(): return f"""Folder not found: {fpath} \nPlease note that this command only works from inside a rapydo-like repository Verify that you are in the right folder, now you are in: {Path.cwd()} """ return None
def create( project_name: str = typer.Argument(..., help="Name of your project"), auth: AuthTypes = typer.Option(..., "--auth", help="Auth service to enable"), frontend: FrontendTypes = typer.Option( ..., "--frontend", help="Frontend framework to enable"), extend: str = typer.Option(None, "--extend", help="Extend from another project"), services: List[ServiceTypes] = typer.Option( [], "--service", "-s", help="Service to be enabled (multiple is enabled)", shell_complete=Application.autocomplete_service, ), origin_url: Optional[str] = typer.Option( None, "--origin-url", help="Set the git origin url for the project"), envs: List[str] = typer.Option( None, "--env", "-e", help= "Command separated list of ENV=VALUE to be added in project_configuration", ), force_current: bool = typer.Option( False, "--current", help="Force creation in current folder", show_default=False, ), force: bool = typer.Option( False, "--force", help="Force files overwriting", show_default=False, ), auto: bool = typer.Option( True, "--no-auto", help="Disable automatic project creation", show_default=False, ), add_optionals: bool = typer.Option( False, "--add-optionals", help="Include all optionals files (html templates and customizers)", show_default=False, ), ) -> None: Application.print_command( Application.serialize_parameter("--auth", auth), Application.serialize_parameter("--frontend", frontend), Application.serialize_parameter("--extend", extend, IF=extend), Application.serialize_parameter("--service", services), Application.serialize_parameter("--origin-url", origin_url, IF=origin_url), Application.serialize_parameter("--env", envs), Application.serialize_parameter("--current", force_current, IF=force_current), Application.serialize_parameter("--force", force, IF=force), Application.serialize_parameter("--auto", auto, IF=auto), Application.serialize_parameter("--add-optionals", add_optionals), Application.serialize_parameter("", project_name), ) Application.get_controller().controller_init() if extend is not None: if project_name == extend: print_and_exit("A project cannot extend itself") if not PROJECT_DIR.joinpath(extend).is_dir(): print_and_exit("Invalid extend value: project {} not found", extend) services_list: List[str] = [service.value for service in services] create_project( project_name=project_name, auth=auth.value, frontend=frontend.value, services=services_list, extend=extend, envs=envs, auto=auto, force=force, force_current=force_current, add_optionals=add_optionals, ) log.info("Project {} successfully created", project_name) git_repo = git.get_repo(".") if git_repo is None: git_repo = git.init(".") print("\nYou can now init and start the project:\n") current_origin = git.get_origin(git_repo) if current_origin is None: if origin_url is None: # pragma: no cover print( "git remote add origin https://your_remote_git/your_project.git" ) else: git_repo.create_remote("origin", origin_url) print("rapydo init") print("rapydo pull") print("rapydo start")
def test_base(capfd: Capture) -> None: execute_outside(capfd, "check") create_project( capfd=capfd, name="third", auth="postgres", frontend="angular", ) init_project(capfd) repo = git.get_repo("submodules/http-api") git.switch_branch(repo, "0.7.6") exec_command( capfd, "check -i main", f"http-api: wrong branch 0.7.6, expected {__version__}", f"You can fix it with {colors.RED}rapydo init{colors.RESET}", ) init_project(capfd) with TemporaryRemovePath(DATA_DIR): exec_command( capfd, "check -i main --no-git --no-builds", "Folder not found: data", "Please note that this command only works from inside a rapydo-like repo", "Verify that you are in the right folder, now you are in: ", ) with TemporaryRemovePath(Path("projects/third/builds")): exec_command( capfd, "check -i main --no-git --no-builds", "Project third is invalid: required folder not found projects/third/builds", ) with TemporaryRemovePath(Path(".gitignore")): exec_command( capfd, "check -i main --no-git --no-builds", "Project third is invalid: required file not found .gitignore", ) # Add a custom image to extend base backend image: with open("projects/third/confs/commons.yml", "a") as f: f.write( """ services: backend: build: ${PROJECT_DIR}/builds/backend image: third/backend:${RAPYDO_VERSION} """ ) os.makedirs("projects/third/builds/backend") with open("projects/third/builds/backend/Dockerfile", "w+") as f: f.write( f""" FROM rapydo/backend:{__version__} RUN mkdir xyz """ ) # Skipping main because we are on a fake git repository exec_command( capfd, "check -i main", f" image, execute {colors.RED}rapydo pull", f" image, execute {colors.RED}rapydo build", f"Compose is installed with version {COMPOSE_VERSION}", f"Buildx is installed with version {BUILDX_VERSION}", "Checks completed", ) exec_command( capfd, "--stack invalid check -i main", "Failed to read projects/third/confs/invalid.yml: File does not exist", ) os.mkdir("submodules/rapydo-confs") exec_command( capfd, "check -i main --no-git --no-builds", "Project third contains an obsolete file or folder: submodules/rapydo-confs", ) shutil.rmtree("submodules/rapydo-confs") # Test selection with two projects create_project( capfd=capfd, name="justanother", auth="postgres", frontend="no", ) os.remove(".projectrc") exec_command( capfd, "check -i main --no-git --no-builds", "Multiple projects found, please use --project to specify one of the following", ) # Test with zero projects with TemporaryRemovePath(Path("projects")): os.mkdir("projects") # in this case SystemExit is raised in the command init... with pytest.raises(SystemExit): exec_command( capfd, "check -i main --no-git --no-builds", "No project found (is projects folder empty?)", ) shutil.rmtree("projects") exec_command( capfd, "-p third check -i main --no-git --no-builds", "Checks completed", ) # Numbers are not allowed as first characters pname = "2invalidcharacter" os.makedirs(f"projects/{pname}") exec_command( capfd, f"-p {pname} check -i main --no-git --no-builds", "Wrong project name, found invalid characters: 2", ) shutil.rmtree(f"projects/{pname}") invalid_characters = { "_": "_", "-": "-", "C": "C", # Invalid characters in output are ordered # Numbers are allowed if not leading "_C-2": "-C_", } # Check invalid and reserved project names for invalid_key, invalid_value in invalid_characters.items(): pname = f"invalid{invalid_key}character" os.makedirs(f"projects/{pname}") exec_command( capfd, f"-p {pname} check -i main --no-git --no-builds", f"Wrong project name, found invalid characters: {invalid_value}", ) shutil.rmtree(f"projects/{pname}") os.makedirs("projects/celery") exec_command( capfd, "-p celery check -i main --no-git --no-builds", "You selected a reserved name, invalid project name: celery", ) shutil.rmtree("projects/celery") exec_command( capfd, "-p fourth check -i main --no-git --no-builds", "Wrong project fourth", "Select one of the following: ", ) # Test init of data folders shutil.rmtree(LOGS_FOLDER) assert not LOGS_FOLDER.is_dir() # Let's restore .projectrc and data/logs init_project(capfd, "--project third") assert LOGS_FOLDER.is_dir() exec_command( capfd, "check -i main --no-git --no-builds", "Checks completed", ) # Test dirty repo fin = open("submodules/do/new_file", "wt+") fin.write("xyz") fin.close() exec_command( capfd, "check -i main", "You have unstaged files on do", "Untracked files:", "submodules/do/new_file", ) with open(".gitattributes", "a") as a_file: a_file.write("\n") a_file.write("# new line") exec_command( capfd, "check -i main", ".gitattributes changed, " f"please execute {colors.RED}rapydo upgrade --path .gitattributes", ) exec_command( capfd, "--prod check -i main --no-git --no-builds", "The following variables are missing in your configuration", "You can fix this error by updating your .projectrc file", ) # Default ALCHEMY_PASSWORD has as score of 2 exec_command( capfd, "-e MIN_PASSWORD_SCORE=3 check -i main --no-git --no-builds", "The password used in ALCHEMY_PASSWORD is weak", ) exec_command( capfd, "-e MIN_PASSWORD_SCORE=4 check -i main --no-git --no-builds", "The password used in ALCHEMY_PASSWORD is very weak", ) exec_command( capfd, "-e MIN_PASSWORD_SCORE=4 -e AUTH_DEFAULT_PASSWORD=x check -i main --no-git --no-builds", "The password used in AUTH_DEFAULT_PASSWORD is extremely weak", ) exec_command( capfd, "--prod init -f", "Created default .projectrc file", "Project initialized", ) exec_command( capfd, "--prod check -i main --no-git --no-builds", "Checks completed", ) if Configuration.swarm_mode: # Skipping main because we are on a fake git repository exec_command( capfd, "check -i main", "Swarm is correctly initialized", "Checks completed", ) docker = Docker() docker.client.swarm.leave(force=True) exec_command( capfd, "check -i main", f"Swarm is not initialized, please execute {colors.RED}rapydo init", ) exec_command( capfd, "init", "Swarm is now initialized", "Project initialized", ) exec_command( capfd, "check -i main", "Swarm is correctly initialized", "Checks completed", ) check = "check -i main --no-git --no-builds" exec_command( capfd, f"-e ASSIGNED_MEMORY_BACKEND=50G {check}", "Your deployment requires 50GB of RAM but your nodes only have", # The error does not halt the checks execution "Checks completed", ) exec_command( capfd, f"-e ASSIGNED_CPU_BACKEND=50.0 {check}", "Your deployment requires ", " cpus but your nodes only have ", # The error does not halt the checks execution "Checks completed", ) exec_command( capfd, f"-e DEFAULT_SCALE_BACKEND=55 -e ASSIGNED_MEMORY_BACKEND=1G {check}", "Your deployment requires 55GB of RAM but your nodes only have", # The error does not halt the checks execution "Checks completed", ) exec_command( capfd, f"-e DEFAULT_SCALE_BACKEND=50 -e ASSIGNED_CPU_BACKEND=1.0 {check}", "Your deployment requires ", " cpus but your nodes only have ", # The error does not halt the checks execution "Checks completed", )
def test_init(capfd: Capture, faker: Faker) -> None: execute_outside(capfd, "init") create_project( capfd=capfd, name=random_project_name(faker), auth="postgres", frontend="no", ) exec_command( capfd, "check -i main", "Repo https://github.com/rapydo/http-api.git missing as submodules/http-api.", "You should init your project", ) if Configuration.swarm_mode: exec_command( capfd, "-e HEALTHCHECK_INTERVAL=1s -e SWARM_MANAGER_ADDRESS=127.0.0.1 init", "docker compose is installed", "Initializing Swarm with manager IP 127.0.0.1", "Swarm is now initialized", "Project initialized", ) docker = Docker() docker.client.swarm.leave(force=True) local_ip = system.get_local_ip(production=False) exec_command( capfd, "-e HEALTHCHECK_INTERVAL=1s -e SWARM_MANAGER_ADDRESS= init", "docker compose is installed", "Swarm is now initialized", f"Initializing Swarm with manager IP {local_ip}", "Project initialized", ) exec_command( capfd, "init", "Swarm is already initialized", "Project initialized", ) else: init_project(capfd) repo = git.get_repo("submodules/http-api") git.switch_branch(repo, "0.7.6") exec_command( capfd, "init", f"Switched http-api branch from 0.7.6 to {__version__}", f"build-templates already set on branch {__version__}", f"do already set on branch {__version__}", ) os.rename("submodules", "submodules.bak") os.mkdir("submodules") # This is to re-fill the submodules folder, # these folder will be removed by the next init exec_command(capfd, "init", "Project initialized") modules_path = Path("submodules.bak").resolve() with TemporaryRemovePath(Path("submodules.bak/do")): exec_command( capfd, f"init --submodules-path {modules_path}", "Submodule do not found in ", ) exec_command( capfd, f"init --submodules-path {modules_path}", "Path submodules/http-api already exists, removing", "Project initialized", ) assert os.path.islink("submodules/do") assert not os.path.islink("submodules.bak/do") # Init again, this time in submodules there are links... # and will be removed as well as the folders exec_command( capfd, f"init --submodules-path {modules_path}", "Path submodules/http-api already exists, removing", "Project initialized", ) exec_command( capfd, "init --submodules-path invalid/path", "Local path not found: invalid/path", ) exec_command( capfd, "--prod init -f", "Created default .projectrc file", "Project initialized", ) exec_command( capfd, "--prod -e MYVAR=MYVAL init -f", "Created default .projectrc file", "Project initialized", ) with open(".projectrc") as projectrc: lines = [line.strip() for line in projectrc.readlines()] assert "MYVAR: MYVAL" in lines
def test_install(capfd: Capture, faker: Faker) -> None: execute_outside(capfd, "install") project = random_project_name(faker) create_project( capfd=capfd, name=project, auth="postgres", frontend="no", ) init_project(capfd) # Initially the controller is installed from pip exec_command( capfd, "update -i main", "Controller not updated because it is installed outside this project", "Installation path is ", ", the current folder is ", "All updated", ) with TemporaryRemovePath(SUBMODULES_DIR.joinpath("do")): exec_command( capfd, "install", "missing as submodules/do. You should init your project", ) exec_command(capfd, "install 100.0", "Invalid version") exec_command(capfd, "install docker", "Docker current version:", "Docker installed version:") exec_command(capfd, "install compose", "Docker compose is installed") exec_command( capfd, "install buildx", "Docker buildx current version:", "Docker buildx installed version:", ) exec_command(capfd, "install auto") r = git.get_repo("submodules/do") git.switch_branch(r, "0.7.6") exec_command( capfd, "install", f"Controller repository switched to {__version__}", ) # Here the controller is installed in editable mode from the correct submodules # folder (this is exactly the default normal condition) exec_command( capfd, "update -i main", # Controller installed from {} and updated "Controller installed from ", " and updated", "All updated", ) # Install the controller from a linked folder to verify that the post-update checks # are able to correctly resolve symlinks # ########################################################### # Copied from test_init_check_update.py from here... SUBMODULES_DIR.rename("submodules.bak") SUBMODULES_DIR.mkdir() # This is to re-fill the submodules folder, # these folder will be removed by the next init exec_command(capfd, "init", "Project initialized") modules_path = Path("submodules.bak").resolve() exec_command( capfd, f"init --submodules-path {modules_path}", "Path submodules/http-api already exists, removing", "Project initialized", ) # ... to here # ########################################################### exec_command( capfd, "update -i main", # Controller installed from {} and updated "Controller installed from ", " and updated", "All updated", ) # This test will change the required version pconf = f"projects/{project}/project_configuration.yaml" # Read and change the content fin = open(pconf) data = fin.read() data = data.replace(f'rapydo: "{__version__}"', 'rapydo: "0.7.6"') fin.close() # Write the new content fin = open(pconf, "wt") fin.write(data) fin.close() exec_command( capfd, "version", f"This project is not compatible with rapydo version {__version__}", "Please downgrade rapydo to version 0.7.6 or modify this project", ) # Read and change the content fin = open(pconf) data = fin.read() data = data.replace('rapydo: "0.7.6"', 'rapydo: "99.99.99"') fin.close() # Write the new content fin = open(pconf, "wt") fin.write(data) fin.close() exec_command( capfd, "version", f"This project is not compatible with rapydo version {__version__}", "Please upgrade rapydo to version 99.99.99 or modify this project", ) exec_command(capfd, "install --no-editable 0.8") exec_command(capfd, "install --no-editable") exec_command(capfd, "install")