Skip to content

Commit

Permalink
Merge pull request #164 from baloise/fix/var-strings
Browse files Browse the repository at this point in the history
New variable syntax
  • Loading branch information
christiansiegel committed Aug 3, 2021
2 parents 77df7cf + 8452a03 commit c047e45
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 50 deletions.
12 changes: 6 additions & 6 deletions docs/includes/preview-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Make sure that your *app repository* contains a `.gitops.config.yaml` file. This
apiVersion: v1
applicationName: app-xy
previewConfig:
host: {PREVIEW_NAMESPACE}.example.tld
host: '{PREVIEW_NAMESPACE}.example.tld'
# template: # optional section
# organisation: templates # optional (default: target.organisation)
# repository: template-repo # optional (default: target.repository)
Expand All @@ -40,18 +40,18 @@ previewConfig:
organisation: deployments
repository: deployment-config-repo
# branch: master # optional (defaults to repo's default branch)
namespace: {APPLICATION_NAME}-{PREVIEW_ID_HASH}-preview # optional (default: '{APPLICATION_NAME}-{PREVIEW_ID}-{PREVIEW_ID_HASH}-preview',
# Invalid characters in PREVIEW_ID will be replaced. PREVIEW_ID will be
# truncated if max namespace length exceeds 63 chars.)
namespace: '{APPLICATION_NAME}-{PREVIEW_ID_HASH}-preview' # optional (default: '{APPLICATION_NAME}-{PREVIEW_ID}-{PREVIEW_ID_HASH}-preview',
# Invalid characters in PREVIEW_ID will be replaced. PREVIEW_ID will be
# truncated if max namespace length exceeds 63 chars.)
replace:
Chart.yaml:
- path: name
value: {PREVIEW_NAMESPACE}
value: '{PREVIEW_NAMESPACE}'
values.yaml:
- path: app.image
value: registry.example.tld/my-app:{GIT_HASH}
- path: route.host
value: {PREVIEW_HOST}
value: '{PREVIEW_HOST}'
```
#### Variables
Expand Down
66 changes: 46 additions & 20 deletions gitopscli/gitops_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from gitopscli.gitops_exception import GitOpsException

_MAX_NAMESPACE_LENGTH = 63
_VARIABLE_REGEX = re.compile(r"{(\w+)}")
_VARIABLE_REGEX = re.compile(r"\${(\w+)}")


@dataclass(frozen=True)
Expand Down Expand Up @@ -44,7 +44,7 @@ def __init__(self, path: str, value_template: str):
def get_value(self, context: PreviewContext) -> str:
val = self.value_template
for variable, value_func in self.__VARIABLE_MAPPERS.items():
val = val.replace(f"{{{variable}}}", value_func(context))
val = val.replace(f"${{{variable}}}", value_func(context))
return val

api_version: int
Expand All @@ -66,7 +66,7 @@ def get_value(self, context: PreviewContext) -> str:

@property
def preview_template_path(self) -> str:
return self.preview_template_path_template.replace("{APPLICATION_NAME}", self.application_name)
return self.preview_template_path_template.replace("${APPLICATION_NAME}", self.application_name)

def __post_init__(self) -> None:
assert isinstance(self.application_name, str), "application_name of wrong type!"
Expand Down Expand Up @@ -98,30 +98,30 @@ def __post_init__(self) -> None:

def get_preview_host(self, preview_id: str) -> str:
preview_host = self.preview_host_template
preview_host = preview_host.replace("{APPLICATION_NAME}", self.application_name)
preview_host = preview_host.replace("{PREVIEW_ID_HASH}", self.create_preview_id_hash(preview_id))
preview_host = preview_host.replace("{PREVIEW_ID}", self.__sanitize(preview_id))
preview_host = preview_host.replace("{PREVIEW_NAMESPACE}", self.get_preview_namespace(preview_id))
preview_host = preview_host.replace("${APPLICATION_NAME}", self.application_name)
preview_host = preview_host.replace("${PREVIEW_ID_HASH}", self.create_preview_id_hash(preview_id))
preview_host = preview_host.replace("${PREVIEW_ID}", self.__sanitize(preview_id))
preview_host = preview_host.replace("${PREVIEW_NAMESPACE}", self.get_preview_namespace(preview_id))
return preview_host

def get_preview_namespace(self, preview_id: str) -> str:
preview_namespace = self.preview_target_namespace_template
preview_namespace = preview_namespace.replace("{APPLICATION_NAME}", self.application_name)
preview_namespace = preview_namespace.replace("{PREVIEW_ID_HASH}", self.create_preview_id_hash(preview_id))
preview_namespace = preview_namespace.replace("${APPLICATION_NAME}", self.application_name)
preview_namespace = preview_namespace.replace("${PREVIEW_ID_HASH}", self.create_preview_id_hash(preview_id))

current_length = len(preview_namespace) - len("{PREVIEW_ID}")
current_length = len(preview_namespace) - len("${PREVIEW_ID}")
remaining_length = _MAX_NAMESPACE_LENGTH - current_length

if remaining_length < 1:
preview_namespace = preview_namespace.replace("{PREVIEW_ID}", "")
preview_namespace = preview_namespace.replace("${PREVIEW_ID}", "")
raise GitOpsException(
f"Preview namespace is too long (max {_MAX_NAMESPACE_LENGTH} chars): "
f"{preview_namespace} ({len(preview_namespace)} chars)"
)

sanitized_preview_id = self.__sanitize(preview_id, remaining_length)

preview_namespace = preview_namespace.replace("{PREVIEW_ID}", sanitized_preview_id)
preview_namespace = preview_namespace.replace("${PREVIEW_ID}", sanitized_preview_id)
preview_namespace = preview_namespace.lower()

invalid_character = re.search(r"[^a-z0-9-]", preview_namespace)
Expand Down Expand Up @@ -216,11 +216,13 @@ def parse(self) -> GitOpsConfig:
return self.__parse_v0()
if api_version == "v1":
return self.__parse_v1()
if api_version == "v2_beta":
return self.__parse_v2()
raise GitOpsException(f"GitOps config apiVersion '{api_version}' is not supported!")

def __parse_v0(self) -> GitOpsConfig:
replacements: Dict[str, List[GitOpsConfig.Replacement]] = {
"Chart.yaml": [GitOpsConfig.Replacement("name", "{PREVIEW_NAMESPACE}")],
"Chart.yaml": [GitOpsConfig.Replacement("name", "${PREVIEW_NAMESPACE}")],
"values.yaml": [],
}
replacement_dicts = self.__get_list_value("previewConfig.replace")
Expand All @@ -247,7 +249,7 @@ def __parse_v0(self) -> GitOpsConfig:
variable = "PREVIEW_HOST" # backwards compatability
if variable == "GIT_COMMIT":
variable = "GIT_HASH" # backwards compatability
replacements["values.yaml"].append(GitOpsConfig.Replacement(path, f"{{{variable}}}"))
replacements["values.yaml"].append(GitOpsConfig.Replacement(path, f"${{{variable}}}"))

preview_target_organisation = self.__get_string_value("deploymentConfig.org")
preview_target_repository = self.__get_string_value("deploymentConfig.repository")
Expand All @@ -256,20 +258,44 @@ def __parse_v0(self) -> GitOpsConfig:
api_version=0,
application_name=self.__get_string_value("deploymentConfig.applicationName"),
preview_host_template=self.__get_string_value("previewConfig.route.host.template").replace(
"{SHA256_8CHAR_BRANCH_HASH}", "{PREVIEW_ID_HASH}" # backwards compatibility
"{SHA256_8CHAR_BRANCH_HASH}", "${PREVIEW_ID_HASH}" # backwards compatibility
),
preview_template_organisation=preview_target_organisation,
preview_template_repository=preview_target_repository,
preview_template_path_template=f".preview-templates/{{APPLICATION_NAME}}",
preview_template_path_template=".preview-templates/${APPLICATION_NAME}",
preview_template_branch=None, # use default branch
preview_target_organisation=preview_target_organisation,
preview_target_repository=preview_target_repository,
preview_target_branch=None, # use default branch
preview_target_namespace_template=f"{{APPLICATION_NAME}}-{{PREVIEW_ID_HASH}}-preview",
preview_target_namespace_template="${APPLICATION_NAME}-${PREVIEW_ID_HASH}-preview",
replacements=replacements,
)

def __parse_v1(self) -> GitOpsConfig:
config = self.__parse_v2()
# add $ in front of variables for backwards compatability (e.g. ${FOO}):
add_var_dollar: Callable[[str], str] = lambda template: re.sub(r"(^|[^\$])({(\w+)})", r"\1$\2", template)
replacements: Dict[str, List[GitOpsConfig.Replacement]] = {}
for filename, file_replacements in config.replacements.items():
replacements[filename] = [
GitOpsConfig.Replacement(r.path, add_var_dollar(r.value_template)) for r in file_replacements
]
return GitOpsConfig(
api_version=1,
application_name=config.application_name,
preview_host_template=add_var_dollar(config.preview_host_template),
preview_template_organisation=config.preview_template_organisation,
preview_template_repository=config.preview_template_repository,
preview_template_path_template=add_var_dollar(config.preview_template_path_template),
preview_template_branch=config.preview_template_branch,
preview_target_organisation=config.preview_target_organisation,
preview_target_repository=config.preview_target_repository,
preview_target_branch=config.preview_target_branch,
preview_target_namespace_template=add_var_dollar(config.preview_target_namespace_template),
replacements=replacements,
)

def __parse_v2(self) -> GitOpsConfig:
preview_target_organisation = self.__get_string_value("previewConfig.target.organisation")
preview_target_repository = self.__get_string_value("previewConfig.target.repository")
preview_target_branch = self.__get_string_value_or_none("previewConfig.target.branch")
Expand Down Expand Up @@ -298,7 +324,7 @@ def __parse_v1(self) -> GitOpsConfig:
replacements[filename].append(GitOpsConfig.Replacement(path, value))

return GitOpsConfig(
api_version=1,
api_version=2,
application_name=self.__get_string_value("applicationName"),
preview_host_template=self.__get_string_value("previewConfig.host"),
preview_template_organisation=self.__get_string_value_or_default(
Expand All @@ -308,15 +334,15 @@ def __parse_v1(self) -> GitOpsConfig:
"previewConfig.template.repository", preview_target_repository
),
preview_template_path_template=self.__get_string_value_or_default(
"previewConfig.template.path", f".preview-templates/{{APPLICATION_NAME}}"
"previewConfig.template.path", ".preview-templates/${APPLICATION_NAME}"
),
preview_template_branch=self.__get_string_value_or_none("previewConfig.template.branch")
or preview_target_branch,
preview_target_organisation=preview_target_organisation,
preview_target_repository=preview_target_repository,
preview_target_branch=preview_target_branch,
preview_target_namespace_template=self.__get_string_value_or_default(
"previewConfig.target.namespace", f"{{APPLICATION_NAME}}-{{PREVIEW_ID}}-{{PREVIEW_ID_HASH}}-preview"
"previewConfig.target.namespace", "${APPLICATION_NAME}-${PREVIEW_ID}-${PREVIEW_ID_HASH}-preview",
),
replacements=replacements,
)
12 changes: 6 additions & 6 deletions tests/commands/test_create_preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,22 @@ def setUp(self):

self.load_gitops_config_mock = self.monkey_patch(load_gitops_config)
self.load_gitops_config_mock.return_value = GitOpsConfig(
api_version=1,
api_version=2,
application_name="my-app",
preview_host_template="app.xy-{PREVIEW_ID_HASH}.example.tld",
preview_host_template="app.xy-${PREVIEW_ID_HASH}.example.tld",
preview_template_organisation="PREVIEW_TEMPLATE_ORG",
preview_template_repository="PREVIEW_TEMPLATE_REPO",
preview_template_path_template=".preview-templates/my-app",
preview_template_branch="template-branch",
preview_target_organisation="PREVIEW_TARGET_ORG",
preview_target_repository="PREVIEW_TARGET_REPO",
preview_target_branch=None,
preview_target_namespace_template=f"my-app-{{PREVIEW_ID_HASH}}-preview",
preview_target_namespace_template="my-app-${PREVIEW_ID_HASH}-preview",
replacements={
"Chart.yaml": [GitOpsConfig.Replacement(path="name", value_template="{PREVIEW_NAMESPACE}"),],
"Chart.yaml": [GitOpsConfig.Replacement(path="name", value_template="${PREVIEW_NAMESPACE}"),],
"values.yaml": [
GitOpsConfig.Replacement(path="image.tag", value_template="{GIT_HASH}"),
GitOpsConfig.Replacement(path="route.host", value_template="{PREVIEW_HOST}"),
GitOpsConfig.Replacement(path="image.tag", value_template="${GIT_HASH}"),
GitOpsConfig.Replacement(path="route.host", value_template="${PREVIEW_HOST}"),
],
},
)
Expand Down
2 changes: 1 addition & 1 deletion tests/commands/test_delete_preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def setUp(self):
preview_target_organisation="PREVIEW_TARGET_ORG",
preview_target_repository="PREVIEW_TARGET_REPO",
preview_target_branch="target-branch",
preview_target_namespace_template=f"APP-{{PREVIEW_ID_HASH}}-preview",
preview_target_namespace_template="APP-${PREVIEW_ID_HASH}-preview",
replacements={},
)

Expand Down
12 changes: 6 additions & 6 deletions tests/test_gitops_config_v0.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def test_preview_target_branch_is_none(self):

def test_route_host_template(self):
config = self.load()
self.assertEqual(config.preview_host_template, "my-{PREVIEW_ID_HASH}-host-template")
self.assertEqual(config.preview_host_template, "my-${PREVIEW_ID_HASH}-host-template")

def test_route_host(self):
config = self.load()
Expand All @@ -97,7 +97,7 @@ def test_route_host_template_not_a_string(self):

def test_namespace_template(self):
config = self.load()
self.assertEqual(config.preview_target_namespace_template, "{APPLICATION_NAME}-{PREVIEW_ID_HASH}-preview")
self.assertEqual(config.preview_target_namespace_template, "${APPLICATION_NAME}-${PREVIEW_ID_HASH}-preview")

def test_namespace(self):
config = self.load()
Expand All @@ -109,13 +109,13 @@ def test_replacements(self):

self.assertEqual(len(config.replacements["Chart.yaml"]), 1)
self.assertEqual(config.replacements["Chart.yaml"][0].path, "name")
self.assertEqual(config.replacements["Chart.yaml"][0].value_template, "{PREVIEW_NAMESPACE}")
self.assertEqual(config.replacements["Chart.yaml"][0].value_template, "${PREVIEW_NAMESPACE}")

self.assertEqual(len(config.replacements["values.yaml"]), 2)
self.assertEqual(config.replacements["values.yaml"][0].path, "a.b")
self.assertEqual(config.replacements["values.yaml"][0].value_template, "{PREVIEW_HOST}")
self.assertEqual(config.replacements["values.yaml"][0].value_template, "${PREVIEW_HOST}")
self.assertEqual(config.replacements["values.yaml"][1].path, "c.d")
self.assertEqual(config.replacements["values.yaml"][1].value_template, "{GIT_HASH}")
self.assertEqual(config.replacements["values.yaml"][1].value_template, "${GIT_HASH}")

def test_replacements_missing(self):
del self.yaml["previewConfig"]["replace"]
Expand Down Expand Up @@ -147,7 +147,7 @@ def test_replacements_invalid_list_items_variable_not_a_string(self):

def test_replacements_invalid_list_items_unknown_variable(self):
self.yaml["previewConfig"]["replace"][0]["variable"] = "FOO"
self.assert_load_error("Replacement value '{FOO}' for path 'a.b' contains invalid variable: FOO")
self.assert_load_error("Replacement value '${FOO}' for path 'a.b' contains invalid variable: FOO")

def test_replacements_invalid_list_items_invalid_variable(self):
self.yaml["previewConfig"]["replace"][0]["variable"] = "{FOO"
Expand Down
Loading

0 comments on commit c047e45

Please sign in to comment.