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
7 changes: 7 additions & 0 deletions src/TextTemplate/TemplateEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,18 @@
if (args.Length == 0) return string.Empty;
string fmt = args[0]?.ToString() ?? string.Empty;
var rest = args.Skip(1).ToArray();
return SprintfFormatter.Format(fmt, rest);

Check warning on line 160 in src/TextTemplate/TemplateEngine.cs

View workflow job for this annotation

GitHub Actions / build

Argument of type 'object?[]' cannot be used for parameter 'args' of type 'object[]' in 'string SprintfFormatter.Format(string format, params object[] args)' due to differences in the nullability of reference types.
},
["html"] = args => System.Net.WebUtility.HtmlEncode(string.Concat(args.Select(a => a?.ToString()))),
["js"] = args => System.Text.Encodings.Web.JavaScriptEncoder.Default.Encode(string.Concat(args.Select(a => a?.ToString()))),
["urlquery"] = args => Uri.EscapeDataString(string.Concat(args.Select(a => a?.ToString()))),
["quote"] = args =>
{
if (args.Length == 0 || args[0] == null) return string.Empty;
var s = args[0]?.ToString() ?? string.Empty;
s = s.Replace("\"", "\\\"");
return $"\"{s}\"";
},
["len"] = args =>
{
if (args.Length == 0 || args[0] == null) return 0;
Expand Down Expand Up @@ -189,7 +196,7 @@
}
if (current is IDictionary dict)
{
current = dict[key];

Check warning on line 199 in src/TextTemplate/TemplateEngine.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'key' in 'object? IDictionary.this[object key]'.
continue;
}
current = null;
Expand Down
5 changes: 5 additions & 0 deletions tests/TextTemplate.Tests/TestData/k8s-template-data.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{template "deployment" .}}
---
{{template "service" .}}
---
{{template "ingress" .}}
97 changes: 97 additions & 0 deletions tests/TextTemplate.Tests/TestData/k8s-template-deployment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{{define "deployment"}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.appName }}-deployment
namespace: {{ .Values.namespace | default "default" }}
labels:
app: {{ .Values.appName }}
version: {{ .Values.version }}
{{- if .Values.environment }}
environment: {{ .Values.environment }}
{{- end }}
{{- range $key, $value := .Values.customLabels }}
{{ $key }}: {{ $value }}
{{- end }}
spec:
replicas: {{ .Values.replicaCount | default 3 }}
selector:
matchLabels:
app: {{ .Values.appName }}
template:
metadata:
labels:
app: {{ .Values.appName }}
version: {{ .Values.version }}
spec:
containers:
- name: {{ .Values.appName }}
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
ports:
- containerPort: {{ .Values.service.port }}
{{- if .Values.env }}
env:
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
{{- if .Values.resources }}
resources:
{{- if .Values.resources.limits }}
limits:
{{- range $key, $value := .Values.resources.limits }}
{{ $key }}: {{ $value }}
{{- end }}
{{- end }}
{{- if .Values.resources.requests }}
requests:
{{- range $key, $value := .Values.resources.requests }}
{{ $key }}: {{ $value }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.volumeMounts }}
volumeMounts:
{{- range .Values.volumeMounts }}
- name: {{ .name }}
mountPath: {{ .mountPath }}
{{- if .readOnly }}
readOnly: {{ .readOnly }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.volumes }}
volumes:
{{- range .Values.volumes }}
- name: {{ .name }}
{{- if .configMap }}
configMap:
name: {{ .configMap.name }}
{{- else if .secret }}
secret:
secretName: {{ .secret.secretName }}
{{- else if .persistentVolumeClaim }}
persistentVolumeClaim:
claimName: {{ .persistentVolumeClaim.claimName }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.nodeSelector }}
nodeSelector:
{{- range $key, $value := .Values.nodeSelector }}
{{ $key }}: {{ $value }}
{{- end }}
{{- end }}
{{- if .Values.tolerations }}
tolerations:
{{- range .Values.tolerations }}
- key: {{ .key }}
operator: {{ .operator | default "Equal" }}
{{- if .value }}
value: {{ .value }}
{{- end }}
effect: {{ .effect }}
{{- end }}
{{- end }}
{{end}}
41 changes: 41 additions & 0 deletions tests/TextTemplate.Tests/TestData/k8s-template-ingress.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{{define "ingress"}}
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Values.appName }}-ingress
namespace: {{ .Values.namespace | default "default" }}
{{- if .Values.ingress.annotations }}
annotations:
{{- range $key, $value := .Values.ingress.annotations }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType | default "Prefix" }}
backend:
service:
name: {{ $.Values.appName }}-service
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}
{{end}}
22 changes: 22 additions & 0 deletions tests/TextTemplate.Tests/TestData/k8s-template-service.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{{define "service"}}
{{- if .Values.service.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.appName }}-service
namespace: {{ .Values.namespace | default "default" }}
labels:
app: {{ .Values.appName }}
spec:
type: {{ .Values.service.type | default "ClusterIP" }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort | default .Values.service.port }}
protocol: TCP
{{- if .Values.service.nodePort }}
nodePort: {{ .Values.service.nodePort }}
{{- end }}
selector:
app: {{ .Values.appName }}
{{- end }}
{{end}}
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ spec:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort | default .Values.service.port }}
protocol: TCP
{{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }}
{{- if .Values.service.nodePort }}
nodePort: {{ .Values.service.nodePort }}
{{- end }}
selector:
Expand Down
136 changes: 136 additions & 0 deletions tests/TextTemplate.Tests/TestData/single-k8s-expected.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-web-app-deployment
namespace: production
labels:
app: my-web-app
version: "1.2.3"
environment: prod
team: backend
cost-center: engineering
project: web-platform
spec:
replicas: 5
selector:
matchLabels:
app: my-web-app
template:
metadata:
labels:
app: my-web-app
version: "1.2.3"
spec:
containers:
- name: my-web-app
image: myregistry.com/my-web-app:v1.2.3
ports:
- containerPort: 80
env:
- name: DATABASE_URL
value: "postgresql://db.example.com:5432/myapp"
- name: REDIS_HOST
value: "redis.example.com"
- name: LOG_LEVEL
value: "info"
- name: API_KEY
value: "secret-api-key-123"
resources:
limits:
cpu: "1000m"
memory: "1Gi"
requests:
cpu: "500m"
memory: "512Mi"
volumeMounts:
- name: config-volume
mountPath: /etc/config
readOnly: true
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
- name: data-volume
mountPath: /var/data
volumes:
- name: config-volume
configMap:
name: my-web-app-config
- name: secret-volume
secret:
secretName: my-web-app-secrets
- name: data-volume
persistentVolumeClaim:
claimName: my-web-app-data
nodeSelector:
kubernetes.io/os: linux
node-type: web-tier
tolerations:
- key: "node-type"
operator: "Equal"
value: "web-tier"
effect: "NoSchedule"
- key: "dedicated"
operator: "Equal"
value: "web-app"
effect: "NoExecute"
---
apiVersion: v1
kind: Service
metadata:
name: my-web-app-service
namespace: production
labels:
app: my-web-app
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
protocol: TCP
nodePort: 30080
selector:
app: my-web-app
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-web-app-ingress
namespace: production
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/rewrite-target: "/"
spec:
tls:
- hosts:
- myapp.example.com
- api.myapp.example.com
secretName: myapp-tls-cert
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-web-app-service
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: my-web-app-service
port:
number: 80
- host: api.myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-web-app-service
port:
number: 80
19 changes: 17 additions & 2 deletions tests/TextTemplate.Tests/TextTemplate.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,25 @@
<None Include="TestData\operations_expected.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\template.yml">
<None Include="TestData\single-k8s-data.yml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\expected.yml">
<None Include="TestData\single-k8s-expected.yml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\k8s-template-data.yml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\k8s-template-deployment.yml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\k8s-template-service.yml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\k8s-template-ingress.yml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\k8s-template-expected.yml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="TestData\inheritance_template.txt">
Expand Down
Loading
Loading