Feat(template_url): substitute local refs
To execute the CLI in foreign directories .#new-machine needs to get tranformed into /path/to/clan#new-machine Otherwise it might pick-up some random flake that is in scope where the cli started executing
This commit is contained in:
@@ -46,7 +46,7 @@ def with_machine_template(
|
||||
)
|
||||
|
||||
# Get the clan template from the specifier
|
||||
[flake_ref, template_selector] = transform_url("machine", template_ident)
|
||||
[flake_ref, template_selector] = transform_url("machine", template_ident, local_path=flake)
|
||||
template_flake = Flake(flake_ref)
|
||||
template = template_flake.select(template_selector)
|
||||
|
||||
|
||||
@@ -1,40 +1,58 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Protocol
|
||||
|
||||
from clan_lib.errors import ClanError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def transform_url(template_type: str, identifier: str) -> tuple[str, str]:
|
||||
class Flake(Protocol):
|
||||
"""
|
||||
Protocol for a local flake, which has a path attribute.
|
||||
Pass clan_lib.flake.Flake or any other object that implements this protocol.
|
||||
"""
|
||||
|
||||
@property
|
||||
def path(self) -> Path: ...
|
||||
|
||||
|
||||
def transform_url(
|
||||
template_type: str, identifier: str, local_path: Flake
|
||||
) -> tuple[str, str]:
|
||||
"""
|
||||
Transform a template flake ref by injecting the context (clan|machine|disko) into the url.
|
||||
We do this for shorthand notation of URLs.
|
||||
If the attribute selector path is longer than one, don't transform it.
|
||||
If the attribute selector path is explicitly selecting an attribute, we don't transform it.
|
||||
|
||||
:param template_type: The type of the template (clan, machine, disko)
|
||||
:param identifier: The identifier of the template, which can be a flake reference with a fragment.
|
||||
:param local_path: The local flake path, which is used to resolve to a local flake reference, i.e. ".#" shorthand.
|
||||
|
||||
Examples:
|
||||
|
||||
# injects "machine" as context
|
||||
1. injects "machine" as context
|
||||
clan machines create --template .#new-machine
|
||||
or
|
||||
clan machines create --template #new-machine
|
||||
-> .#clan.templates.machine.new-machine
|
||||
|
||||
# injects "clan" as context
|
||||
2. injects "clan" as context
|
||||
clan create --template .#default
|
||||
-> .#clan.templates.clan.default
|
||||
|
||||
# Dont transform explicit paths (e.g. when more than one attribute selector is present)
|
||||
3. Dont transform explicit paths (e.g. when more than one attribute selector is present)
|
||||
clan machines create --template .#clan.templates.machine.new-machine
|
||||
-> .#clan.templates.machine.new-machine
|
||||
|
||||
clan machines create --template .#"new.machine"
|
||||
-> .#clan.templates.machine."new.machine"
|
||||
|
||||
# Builtin templates
|
||||
4. Builtin templates
|
||||
clan machines create --template new.machine
|
||||
-> clanInternals.templates.machine."new.machine"
|
||||
|
||||
# Remote templates
|
||||
5. Remote templates
|
||||
clan machines create --template github:/org/repo#new.machine
|
||||
-> clanInternals.templates.machine."new.machine"
|
||||
|
||||
@@ -55,10 +73,14 @@ def transform_url(template_type: str, identifier: str) -> tuple[str, str]:
|
||||
[flake_ref, selector] = (
|
||||
identifier.split("#", 1) if "#" in identifier else ["", identifier]
|
||||
)
|
||||
# Substitute the flake reference with the local flake path if it is empty or just a dot.
|
||||
# This is required if the command will be executed from a different place, than the local flake root.
|
||||
if not flake_ref or flake_ref == ".":
|
||||
flake_ref = str(local_path.path)
|
||||
|
||||
if "#" not in identifier:
|
||||
# No fragment, so we assume its a builtin template
|
||||
return ("", f'clanInternals.templates.{template_type}."{selector}"')
|
||||
return (flake_ref, f'clanInternals.templates.{template_type}."{selector}"')
|
||||
|
||||
# TODO: implement support for quotes in the tail "a.b".c
|
||||
# If the tail contains a dot, or is quoted we assume its a path and don't transform it.
|
||||
|
||||
@@ -20,8 +20,10 @@ def test_transform_url_self_explizit_dot() -> None:
|
||||
user_input = ".#new-machine"
|
||||
expected_selector = 'clan.templates.machine."new-machine"'
|
||||
|
||||
flake_ref, selector = transform_url(template_type, user_input)
|
||||
assert flake_ref == "."
|
||||
flake_ref, selector = transform_url(
|
||||
template_type, user_input, local_path=local_path
|
||||
)
|
||||
assert flake_ref == str(local_path.path)
|
||||
assert selector == expected_selector
|
||||
|
||||
|
||||
@@ -29,8 +31,10 @@ def test_transform_url_self_no_dot() -> None:
|
||||
user_input = "#new-machine"
|
||||
expected_selector = 'clan.templates.machine."new-machine"'
|
||||
|
||||
flake_ref, selector = transform_url(template_type, user_input)
|
||||
assert flake_ref == ""
|
||||
flake_ref, selector = transform_url(
|
||||
template_type, user_input, local_path=local_path
|
||||
)
|
||||
assert flake_ref == str(local_path.path)
|
||||
assert selector == expected_selector
|
||||
|
||||
|
||||
@@ -38,8 +42,10 @@ def test_transform_url_builtin_template() -> None:
|
||||
user_input = "new-machine"
|
||||
expected_selector = 'clanInternals.templates.machine."new-machine"'
|
||||
|
||||
flake_ref, selector = transform_url(template_type, user_input)
|
||||
assert flake_ref == ""
|
||||
flake_ref, selector = transform_url(
|
||||
template_type, user_input, local_path=local_path
|
||||
)
|
||||
assert flake_ref == str(local_path.path)
|
||||
assert selector == expected_selector
|
||||
|
||||
|
||||
@@ -47,7 +53,9 @@ def test_transform_url_remote_template() -> None:
|
||||
user_input = "github:/org/repo#new-machine"
|
||||
expected_selector = 'clan.templates.machine."new-machine"'
|
||||
|
||||
flake_ref, selector = transform_url(template_type, user_input)
|
||||
flake_ref, selector = transform_url(
|
||||
template_type, user_input, local_path=local_path
|
||||
)
|
||||
|
||||
assert flake_ref == "github:/org/repo"
|
||||
assert selector == expected_selector
|
||||
@@ -57,8 +65,10 @@ def test_transform_url_explicit_path() -> None:
|
||||
user_input = ".#clan.templates.machine.new-machine"
|
||||
expected_selector = "clan.templates.machine.new-machine"
|
||||
|
||||
flake_ref, selector = transform_url(template_type, user_input)
|
||||
assert flake_ref == "."
|
||||
flake_ref, selector = transform_url(
|
||||
template_type, user_input, local_path=local_path
|
||||
)
|
||||
assert flake_ref == str(local_path.path)
|
||||
assert selector == expected_selector
|
||||
|
||||
|
||||
@@ -66,16 +76,20 @@ def test_transform_url_explicit_path() -> None:
|
||||
def test_transform_url_quoted_selector() -> None:
|
||||
user_input = '.#"new.machine"'
|
||||
expected_selector = '"new.machine"'
|
||||
flake_ref, selector = transform_url(template_type, user_input)
|
||||
assert flake_ref == "."
|
||||
flake_ref, selector = transform_url(
|
||||
template_type, user_input, local_path=local_path
|
||||
)
|
||||
assert flake_ref == str(local_path.path)
|
||||
assert selector == expected_selector
|
||||
|
||||
|
||||
def test_single_quote_selector() -> None:
|
||||
user_input = ".#'new.machine'"
|
||||
expected_selector = "'new.machine'"
|
||||
flake_ref, selector = transform_url(template_type, user_input)
|
||||
assert flake_ref == "."
|
||||
flake_ref, selector = transform_url(
|
||||
template_type, user_input, local_path=local_path
|
||||
)
|
||||
assert flake_ref == str(local_path.path)
|
||||
assert selector == expected_selector
|
||||
|
||||
|
||||
@@ -83,7 +97,9 @@ def test_custom_template_path() -> None:
|
||||
user_input = "github:/org/repo#my.templates.custom.machine"
|
||||
expected_selector = "my.templates.custom.machine"
|
||||
|
||||
flake_ref, selector = transform_url(template_type, user_input)
|
||||
flake_ref, selector = transform_url(
|
||||
template_type, user_input, local_path=local_path
|
||||
)
|
||||
assert flake_ref == "github:/org/repo"
|
||||
assert selector == expected_selector
|
||||
|
||||
@@ -93,7 +109,9 @@ def test_full_url_query_and_fragment() -> None:
|
||||
expected_flake_ref = "github:/org/repo?query=param"
|
||||
expected_selector = "my.templates.custom.machine"
|
||||
|
||||
flake_ref, selector = transform_url(template_type, user_input)
|
||||
flake_ref, selector = transform_url(
|
||||
template_type, user_input, local_path=local_path
|
||||
)
|
||||
assert flake_ref == expected_flake_ref
|
||||
assert selector == expected_selector
|
||||
|
||||
@@ -102,15 +120,17 @@ def test_custom_template_type() -> None:
|
||||
user_input = "#my.templates.custom.machine"
|
||||
expected_selector = "my.templates.custom.machine"
|
||||
|
||||
flake_ref, selector = transform_url("custom", user_input)
|
||||
assert flake_ref == ""
|
||||
flake_ref, selector = transform_url("custom", user_input, local_path=local_path)
|
||||
assert flake_ref == str(local_path.path)
|
||||
assert selector == expected_selector
|
||||
|
||||
|
||||
def test_malformed_identifier() -> None:
|
||||
user_input = "github:/org/repo#my.templates.custom.machine#extra"
|
||||
with pytest.raises(ClanError) as exc_info:
|
||||
_flake_ref, _selector = transform_url(template_type, user_input)
|
||||
_flake_ref, _selector = transform_url(
|
||||
template_type, user_input, local_path=local_path
|
||||
)
|
||||
|
||||
assert isinstance(exc_info.value, ClanError)
|
||||
assert (
|
||||
|
||||
Reference in New Issue
Block a user