Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions apis/common/v1alpha1/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Package v1alpha1 holds types shared by every git-provider API group.
//
// It exists for the same reason internal/controllers/common/templating does: the repo and
// localresource controllers keep growing parallel definitions of the same concept, and every
// time they drift a defect ships.
//
// +kubebuilder:object:generate=true
package v1alpha1

// TemplatingError names a file whose content could not be rendered, and why.
//
// Templating failures used to surface only as a single wrapped error on the Synced condition,
// with no indication of WHICH file was at fault — Go's text/template reports positions against
// an anonymous template ("template: template:61: ..."), which is unusable when a sync copies
// more than one file.
type TemplatingError struct {
// Path of the file, relative to the source copy path.
Path string `json:"path"`

// Message is the renderer's error, verbatim.
Message string `json:"message"`
}
22 changes: 22 additions & 0 deletions apis/common/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions apis/localresource/v1alpha1/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package v1alpha1

import (
commonapis "github.com/krateoplatformops/git-provider/apis/common/v1alpha1"
prv1 "github.com/krateoplatformops/provider-runtime/apis/common/v1"
"github.com/krateoplatformops/provider-runtime/pkg/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -164,6 +165,12 @@ type LocalResourceStatus struct {

// TargetBranch: branch where commit was done
TargetBranch string `json:"targetBranch,omitempty"`
// TemplatingErrors: files whose content could not be rendered, with the renderer's own
// message. Populated when a sync fails during templating and cleared on the next
// successful one; a failed sync commits nothing, so this describes what would have been
// published rather than what was.
// +optional
TemplatingErrors []commonapis.TemplatingError `json:"templatingErrors,omitempty"`
}

// +kubebuilder:object:root=true
Expand Down
22 changes: 6 additions & 16 deletions apis/localresource/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions apis/repo/v1alpha1/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package v1alpha1

import (
commonapis "github.com/krateoplatformops/git-provider/apis/common/v1alpha1"
commonv1 "github.com/krateoplatformops/provider-runtime/apis/common/v1"
prv1 "github.com/krateoplatformops/provider-runtime/apis/common/v1"
"github.com/krateoplatformops/provider-runtime/pkg/resource"
Expand Down Expand Up @@ -105,6 +106,12 @@ type RepoStatus struct {

// OriginBranch: branch where commit was done
OriginBranch string `json:"originBranch,omitempty"`
// TemplatingErrors: files whose content could not be rendered, with the renderer's own
// message. Populated when a sync fails during templating and cleared on the next
// successful one; a failed sync commits nothing, so this describes what would have been
// published rather than what was.
// +optional
TemplatingErrors []commonapis.TemplatingError `json:"templatingErrors,omitempty"`
}

// +kubebuilder:object:root=true
Expand Down
22 changes: 6 additions & 16 deletions apis/repo/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions crds/git.krateo.io_localresources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,32 @@ spec:
description: 'TargetCommitId: last commit identifier of the target
LocalResource'
type: string
templatingErrors:
description: |-
TemplatingErrors: files whose content could not be rendered, with the renderer's own
message. Populated when a sync fails during templating and cleared on the next
successful one; a failed sync commits nothing, so this describes what would have been
published rather than what was.
items:
description: |-
TemplatingError names a file whose content could not be rendered, and why.

Templating failures used to surface only as a single wrapped error on the Synced condition,
with no indication of WHICH file was at fault — Go's text/template reports positions against
an anonymous template ("template: template:61: ..."), which is unusable when a sync copies
more than one file.
properties:
message:
description: Message is the renderer's error, verbatim.
type: string
path:
description: Path of the file, relative to the source copy path.
type: string
required:
- message
- path
type: object
type: array
type: object
required:
- spec
Expand Down
26 changes: 26 additions & 0 deletions crds/git.krateo.io_repoes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,32 @@ spec:
description: 'TargetCommitId: last commit identifier of the target
repo'
type: string
templatingErrors:
description: |-
TemplatingErrors: files whose content could not be rendered, with the renderer's own
message. Populated when a sync fails during templating and cleared on the next
successful one; a failed sync commits nothing, so this describes what would have been
published rather than what was.
items:
description: |-
TemplatingError names a file whose content could not be rendered, and why.

Templating failures used to surface only as a single wrapped error on the Synced condition,
with no indication of WHICH file was at fault — Go's text/template reports positions against
an anonymous template ("template: template:61: ..."), which is unusable when a sync copies
more than one file.
properties:
message:
description: Message is the renderer's error, verbatim.
type: string
path:
description: Path of the file, relative to the source copy path.
type: string
required:
- message
- path
type: object
type: array
type: object
required:
- spec
Expand Down
99 changes: 97 additions & 2 deletions docs/local-resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ If `fileName` is not explicitly provided in `spec.fromResource` (and you are not

## Templating

`LocalResource` supports a simple placeholder replacement mechanism. You can define a list of `placeholdersToOverride` in the spec. The provider will look for occurrences of `{{ .placeholderName }}` in the source content and replace them with the corresponding value.
`LocalResource` supports a simple placeholder replacement mechanism. You can define a list of `placeholdersToOverride` in the spec. The provider will look for occurrences of `{% .placeholderName %}` in the source content and replace them with the corresponding value.

```yaml
spec:
Expand All @@ -70,7 +70,102 @@ spec:
- name: replicaCount
value: "3"
```
In your source content, you would use `{{ .environment }}` and `{{ .replicaCount }}`.
In your source content, you would use `{% .environment %}` and `{% .replicaCount %}`.

Rendering is done with Go `text/template` plus the [sprig](https://masterminds.github.io/sprig/) function set.

### When templating is applied

Rendering is **not** a no-op on content that only looks like a template, so it is applied deliberately rather than always:

| `krateo.io/templating-engine` | behaviour |
|---|---|
| *(absent — the default)* | Render **only if** `placeholdersToOverride` is set. Otherwise the file is committed byte-for-byte. |
| `gotemplate` | Always render, including when no placeholders are declared. |
| `none` | Never render, even when placeholders are declared. |

Any other value is rejected with an error rather than silently treated as one of the above.

The default suits the two common cases at once: a resource that declares placeholders gets them substituted, and a resource that declares none is copied verbatim.

### Delimiters

Placeholders use **`{% %}`**, not Go's usual `{{ }}`.

That is deliberate. The files this provider publishes are overwhelmingly Kubernetes manifests and Helm charts, and **Helm owns `{{ }}`**. Sharing the delimiter makes it impossible to publish a chart with substitution at all — `{{ toYaml x }}` and `{{ include "y" . }}` fail (both are Helm builtins, not sprig ones), `{{ .Values.n }}` renders to the literal `<no value>`, and the `{{/* ... */}}` header `helm create` writes into every `_helpers.tpl` is deleted.

With `{% %}` the two coexist — each system renders its own half, at its own time:

```yaml
metadata:
labels:
krateo.io/tenant: {% .tenant %} # substituted at publish time, by this provider
spec:
replicas: {{ .Values.replicas }} # left alone; rendered by Helm at install time
containers:
{{- toYaml .Values.containers | nindent 4 }} # left alone
```

`{% %}` was chosen by testing the alternatives against real YAML: `<< >>` breaks on the merge key `<<: *defaults`, and `[[ ]]` breaks on flow sequences such as `[[1,2],[3,4]]`.

Override with `krateo.io/templating-delims: "left,right"` — for example `"{{,}}"` to use Go's native syntax where the content is known not to be a chart. A malformed value is rejected rather than silently ignored.

Set `gotemplate` when the content templates **without inputs** — sprig functions that need no values:

```yaml
metadata:
annotations:
krateo.io/templating-engine: gotemplate
spec:
fromResource:
fileName: README.md
fromString: |
Generated on {{ now | date "2006-01-02" }} — build {{ uuidv4 }}.
```

Set `none` for authored content that must survive exactly, most often a **Helm chart**. Chart templates are rendered by Helm at install time, not by this provider at commit time, and pushing them through Go `text/template` first either fails or corrupts them:

* `{{ toYaml ... }}`, `{{ include ... }}`, `{{ required ... }}` and `{{ tpl ... }}` are **Helm** builtins, not sprig ones — the sync fails with `function "toYaml" not defined`.
* `{{ .Values.replicas }}` is worse, because it *parses*: it renders against an empty value set and is committed as the literal string `<no value>`.
* `{{/* ... */}}` comment headers — which `helm create` writes at the top of every `_helpers.tpl` — are removed.

```yaml
metadata:
annotations:
krateo.io/templating-engine: none
```

With the default (no annotation and no placeholders) a chart is already copied verbatim; `none` states the intent explicitly and keeps holding if placeholders are added later.


### When templating fails

`status.templatingErrors` lists **every** problem found, not one per file and not just the first.

That matters because `text/template` stops at its first error — a file with three undefined functions reports one, and you would fix, re-sync, and meet the next. Worse, a reference to a value that was never declared is **not an error at all**: it renders as the literal `<no value>` and gets committed. Both are reported here:

```yaml
status:
conditions:
- type: Synced
status: "False"
reason: ReconcileError
message: 'unable to copy files: rendering failed: 4 problems in 1 file
(templates/deployment.yaml); first: template:2:8: no value declared for ".environment"'
templatingErrors:
- path: templates/deployment.yaml
message: 'template:2:8: no value declared for ".environment"'
- path: templates/deployment.yaml
message: 'template:3:13: no value declared for ".a"'
- path: templates/deployment.yaml
message: 'template:3:6: function "toYaml" not defined'
- path: templates/deployment.yaml
message: 'template:4:6: function "include" not defined'
```

A failed sync commits nothing, so this describes what *would* have been published. The list is cleared on the next successful sync.

**One limit, stated honestly.** A syntax error genuinely stops the parser, so for that class there is only ever one report — everything after the broken action is unreachable. You will see a single entry with no `line:column`, which is the parser's own message.

## Custom Commits

Expand Down
9 changes: 9 additions & 0 deletions docs/repo.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ A common use case is **project bootstrapping**. You might have a "golden path" o

## Templating

> **Engines.** `krateo.io/templating-engine` accepts `gotemplate`, `mustache` and `none`. Absent keeps the historical default, **mustache**. `none` is now honoured — it previously fell through and silently got mustache — and an unrecognised value is now rejected rather than silently switching engine.
>
> **`mustache` is legacy.** It also uses `{{ }}`, so it collides with Helm exactly as Go templates did; `AllowMissingVariables` defaults to true, so a key it cannot resolve renders as **empty with no error**; and it cannot be scanned, so `status.templatingErrors` carries no per-problem detail for it. Prefer `gotemplate`.
>
> **Delimiters.** Under `gotemplate`, placeholders use `{% %}`, not `{{ }}`, so they do not collide with Helm syntax in the files being copied. Override with `krateo.io/templating-delims: "left,right"`. See [local-resource.md](local-resource.md#delimiters) for the reasoning.
>
> **Failures.** `status.templatingErrors` lists every file that could not be rendered, with the renderer's message. A failed sync commits nothing.


The `git-provider` supports two templating engines to customize files copied from the source repository: **Mustache** (default) and **Go Templates**.

### Providing Values
Expand Down
26 changes: 26 additions & 0 deletions helm/git-provider-crds/templates/git.krateo.io_localresources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,32 @@ spec:
description: 'TargetCommitId: last commit identifier of the target
LocalResource'
type: string
templatingErrors:
description: |-
TemplatingErrors: files whose content could not be rendered, with the renderer's own
message. Populated when a sync fails during templating and cleared on the next
successful one; a failed sync commits nothing, so this describes what would have been
published rather than what was.
items:
description: |-
TemplatingError names a file whose content could not be rendered, and why.

Templating failures used to surface only as a single wrapped error on the Synced condition,
with no indication of WHICH file was at fault — Go's text/template reports positions against
an anonymous template ("template: template:61: ..."), which is unusable when a sync copies
more than one file.
properties:
message:
description: Message is the renderer's error, verbatim.
type: string
path:
description: Path of the file, relative to the source copy path.
type: string
required:
- message
- path
type: object
type: array
type: object
required:
- spec
Expand Down
Loading