commit 4dc29e012aaff938cd504fe6852860584160fb91
parent df55da1c9a257f1d3b591a19b598d3177f53cb11
Author: MTRNord <MTRNord@users.noreply.github.com>
Date: Thu, 26 Feb 2026 11:39:46 +0100
use proper csv
Signed-off-by: MTRNord <MTRNord@users.noreply.github.com>
Diffstat:
3 files changed, 506 insertions(+), 18 deletions(-)
diff --git a/apps/talos_cluster/opencost/hetzner-pricing.yaml b/apps/talos_cluster/opencost/hetzner-pricing.yaml
@@ -5,21 +5,141 @@ metadata:
namespace: opencost
data:
hetzner.csv: |
- Region,Instance Type,vCPUs,RAM (GiB),Storage (GB),GPU,Cost per Hour,PricingUnit
- fsn1,cax11,2,4,40,0,0.00742,Hour
- hel1,cax11,2,4,40,0,0.00742,Hour
- nbg1,cax11,2,4,40,0,0.00742,Hour
- hel1,cax21,4,8,80,0,0.01321,Hour
- fsn1,cax21,4,8,80,0,0.01321,Hour
- nbg1,cax21,4,8,80,0,0.01321,Hour
- fsn1,cax31,8,16,160,0,0.02643,Hour
- hel1,cax31,8,16,160,0,0.02643,Hour
- nbg1,cax31,8,16,160,0,0.02643,Hour
- hel1,cax41,16,32,320,0,0.05000,Hour
- fsn1,cax41,16,32,320,0,0.05000,Hour
- nbg1,cax41,16,32,320,0,0.05000,Hour
- hel1,cpx11,2,2,40,0,0.00614,Hour
- hel1,cpx21,3,4,80,0,0.01007,Hour
- hel1,cpx31,4,8,160,0,0.01793,Hour
- hel1,cpx41,8,16,240,0,0.03236,Hour
- hel1,cpx51,16,32,360,0,0.06000,Hour
+ # Hetzner Cloud Pricing for OpenCost CSV Provider
+ # Generated: 2026-02-26T11:39:02.783509
+ # Source: Hetzner Cloud API (https://api.hetzner.cloud/v1/pricing)
+ # Prices in EUR per hour (net, excluding VAT)
+ #
+ EndTimestamp,InstanceID,Region,AssetClass,InstanceIDField,InstanceType,MarketPriceHourly,Version
+ ,cpx11,ash,node,metadata.labels.node.kubernetes.io/instance-type,cpx11,0.007200,2026.02
+ ,cpx11,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx11,0.006300,2026.02
+ ,cpx11,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx11,0.006300,2026.02
+ ,cpx11,hil,node,metadata.labels.node.kubernetes.io/instance-type,cpx11,0.007200,2026.02
+ ,cpx11,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx11,0.006300,2026.02
+ ,cpx11,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx11,0.011900,2026.02
+ ,cpx21,ash,node,metadata.labels.node.kubernetes.io/instance-type,cpx21,0.014500,2026.02
+ ,cpx21,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx21,0.011200,2026.02
+ ,cpx21,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx21,0.011200,2026.02
+ ,cpx21,hil,node,metadata.labels.node.kubernetes.io/instance-type,cpx21,0.014500,2026.02
+ ,cpx21,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx21,0.011200,2026.02
+ ,cpx21,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx21,0.022600,2026.02
+ ,cpx31,ash,node,metadata.labels.node.kubernetes.io/instance-type,cpx31,0.025700,2026.02
+ ,cpx31,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx31,0.021100,2026.02
+ ,cpx31,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx31,0.021100,2026.02
+ ,cpx31,hil,node,metadata.labels.node.kubernetes.io/instance-type,cpx31,0.025700,2026.02
+ ,cpx31,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx31,0.021100,2026.02
+ ,cpx31,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx31,0.039700,2026.02
+ ,cpx41,ash,node,metadata.labels.node.kubernetes.io/instance-type,cpx41,0.048100,2026.02
+ ,cpx41,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx41,0.040900,2026.02
+ ,cpx41,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx41,0.040900,2026.02
+ ,cpx41,hil,node,metadata.labels.node.kubernetes.io/instance-type,cpx41,0.048100,2026.02
+ ,cpx41,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx41,0.040900,2026.02
+ ,cpx41,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx41,0.068300,2026.02
+ ,cpx51,ash,node,metadata.labels.node.kubernetes.io/instance-type,cpx51,0.096200,2026.02
+ ,cpx51,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx51,0.087200,2026.02
+ ,cpx51,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx51,0.087200,2026.02
+ ,cpx51,hil,node,metadata.labels.node.kubernetes.io/instance-type,cpx51,0.096200,2026.02
+ ,cpx51,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx51,0.087200,2026.02
+ ,cpx51,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx51,0.123200,2026.02
+ ,cax11,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cax11,0.005300,2026.02
+ ,cax11,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cax11,0.005300,2026.02
+ ,cax11,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cax11,0.005300,2026.02
+ ,cax21,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cax21,0.009600,2026.02
+ ,cax21,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cax21,0.009600,2026.02
+ ,cax21,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cax21,0.009600,2026.02
+ ,cax31,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cax31,0.019200,2026.02
+ ,cax31,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cax31,0.019200,2026.02
+ ,cax31,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cax31,0.019200,2026.02
+ ,cax41,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cax41,0.038400,2026.02
+ ,cax41,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cax41,0.038400,2026.02
+ ,cax41,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cax41,0.038400,2026.02
+ ,ccx13,ash,node,metadata.labels.node.kubernetes.io/instance-type,ccx13,0.020900,2026.02
+ ,ccx13,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,ccx13,0.019200,2026.02
+ ,ccx13,hel1,node,metadata.labels.node.kubernetes.io/instance-type,ccx13,0.019200,2026.02
+ ,ccx13,hil,node,metadata.labels.node.kubernetes.io/instance-type,ccx13,0.020900,2026.02
+ ,ccx13,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,ccx13,0.019200,2026.02
+ ,ccx13,sin,node,metadata.labels.node.kubernetes.io/instance-type,ccx13,0.033700,2026.02
+ ,ccx23,ash,node,metadata.labels.node.kubernetes.io/instance-type,ccx23,0.041700,2026.02
+ ,ccx23,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,ccx23,0.038400,2026.02
+ ,ccx23,hel1,node,metadata.labels.node.kubernetes.io/instance-type,ccx23,0.038400,2026.02
+ ,ccx23,hil,node,metadata.labels.node.kubernetes.io/instance-type,ccx23,0.041700,2026.02
+ ,ccx23,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,ccx23,0.038400,2026.02
+ ,ccx23,sin,node,metadata.labels.node.kubernetes.io/instance-type,ccx23,0.063100,2026.02
+ ,ccx33,ash,node,metadata.labels.node.kubernetes.io/instance-type,ccx33,0.080200,2026.02
+ ,ccx33,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,ccx33,0.076900,2026.02
+ ,ccx33,hel1,node,metadata.labels.node.kubernetes.io/instance-type,ccx33,0.076900,2026.02
+ ,ccx33,hil,node,metadata.labels.node.kubernetes.io/instance-type,ccx33,0.080200,2026.02
+ ,ccx33,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,ccx33,0.076900,2026.02
+ ,ccx33,sin,node,metadata.labels.node.kubernetes.io/instance-type,ccx33,0.119200,2026.02
+ ,ccx43,ash,node,metadata.labels.node.kubernetes.io/instance-type,ccx43,0.160300,2026.02
+ ,ccx43,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,ccx43,0.153800,2026.02
+ ,ccx43,hel1,node,metadata.labels.node.kubernetes.io/instance-type,ccx43,0.153800,2026.02
+ ,ccx43,hil,node,metadata.labels.node.kubernetes.io/instance-type,ccx43,0.160300,2026.02
+ ,ccx43,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,ccx43,0.153800,2026.02
+ ,ccx43,sin,node,metadata.labels.node.kubernetes.io/instance-type,ccx43,0.219600,2026.02
+ ,ccx53,ash,node,metadata.labels.node.kubernetes.io/instance-type,ccx53,0.320500,2026.02
+ ,ccx53,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,ccx53,0.307700,2026.02
+ ,ccx53,hel1,node,metadata.labels.node.kubernetes.io/instance-type,ccx53,0.307700,2026.02
+ ,ccx53,hil,node,metadata.labels.node.kubernetes.io/instance-type,ccx53,0.320500,2026.02
+ ,ccx53,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,ccx53,0.307700,2026.02
+ ,ccx53,sin,node,metadata.labels.node.kubernetes.io/instance-type,ccx53,0.471200,2026.02
+ ,ccx63,ash,node,metadata.labels.node.kubernetes.io/instance-type,ccx63,0.480800,2026.02
+ ,ccx63,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,ccx63,0.461500,2026.02
+ ,ccx63,hel1,node,metadata.labels.node.kubernetes.io/instance-type,ccx63,0.461500,2026.02
+ ,ccx63,hil,node,metadata.labels.node.kubernetes.io/instance-type,ccx63,0.480800,2026.02
+ ,ccx63,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,ccx63,0.461500,2026.02
+ ,ccx63,sin,node,metadata.labels.node.kubernetes.io/instance-type,ccx63,0.772400,2026.02
+ ,cpx12,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx12,0.009600,2026.02
+ ,cpx22,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx22,0.009600,2026.02
+ ,cpx22,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx22,0.009600,2026.02
+ ,cpx22,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx22,0.009600,2026.02
+ ,cpx22,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx22,0.019200,2026.02
+ ,cpx32,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx32,0.016800,2026.02
+ ,cpx32,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx32,0.016800,2026.02
+ ,cpx32,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx32,0.016800,2026.02
+ ,cpx32,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx32,0.040000,2026.02
+ ,cpx42,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx42,0.031200,2026.02
+ ,cpx42,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx42,0.031200,2026.02
+ ,cpx42,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx42,0.031200,2026.02
+ ,cpx42,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx42,0.068900,2026.02
+ ,cpx52,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx52,0.044900,2026.02
+ ,cpx52,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx52,0.044900,2026.02
+ ,cpx52,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx52,0.044900,2026.02
+ ,cpx52,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx52,0.096100,2026.02
+ ,cpx62,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx62,0.061700,2026.02
+ ,cpx62,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx62,0.061700,2026.02
+ ,cpx62,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx62,0.061700,2026.02
+ ,cpx62,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx62,0.123400,2026.02
+ ,cx23,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cx23,0.004800,2026.02
+ ,cx23,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cx23,0.004800,2026.02
+ ,cx23,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cx23,0.004800,2026.02
+ ,cx33,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cx33,0.008000,2026.02
+ ,cx33,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cx33,0.008000,2026.02
+ ,cx33,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cx33,0.008000,2026.02
+ ,cx43,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cx43,0.014400,2026.02
+ ,cx43,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cx43,0.014400,2026.02
+ ,cx43,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cx43,0.014400,2026.02
+ ,cx53,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cx53,0.027200,2026.02
+ ,cx53,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cx53,0.027200,2026.02
+ ,cx53,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cx53,0.027200,2026.02
+ ,lb11,fsn1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb11,0.008800,2026.02
+ ,lb11,nbg1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb11,0.008800,2026.02
+ ,lb11,hel1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb11,0.008800,2026.02
+ ,lb11,ash,node,metadata.labels.load-balancer.hetzner.cloud/type,lb11,0.008800,2026.02
+ ,lb11,hil,node,metadata.labels.load-balancer.hetzner.cloud/type,lb11,0.008800,2026.02
+ ,lb11,sin,node,metadata.labels.load-balancer.hetzner.cloud/type,lb11,0.008800,2026.02
+ ,lb21,fsn1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb21,0.025300,2026.02
+ ,lb21,nbg1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb21,0.025300,2026.02
+ ,lb21,hel1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb21,0.025300,2026.02
+ ,lb21,ash,node,metadata.labels.load-balancer.hetzner.cloud/type,lb21,0.025300,2026.02
+ ,lb21,hil,node,metadata.labels.load-balancer.hetzner.cloud/type,lb21,0.025300,2026.02
+ ,lb21,sin,node,metadata.labels.load-balancer.hetzner.cloud/type,lb21,0.025300,2026.02
+ ,lb31,fsn1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb31,0.049500,2026.02
+ ,lb31,nbg1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb31,0.049500,2026.02
+ ,lb31,hel1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb31,0.049500,2026.02
+ ,lb31,ash,node,metadata.labels.load-balancer.hetzner.cloud/type,lb31,0.049500,2026.02
+ ,lb31,hil,node,metadata.labels.load-balancer.hetzner.cloud/type,lb31,0.049500,2026.02
+ ,lb31,sin,node,metadata.labels.load-balancer.hetzner.cloud/type,lb31,0.049500,2026.02
+ ,ssd,fsn1,pv,spec.storageClassName,hcloud-volumes,0.00006027,2026.02
+ ,ssd,nbg1,pv,spec.storageClassName,hcloud-volumes,0.00006027,2026.02
+ ,ssd,hel1,pv,spec.storageClassName,hcloud-volumes,0.00006027,2026.02
diff --git a/scripts/hetzner_pricing.csv b/scripts/hetzner_pricing.csv
@@ -0,0 +1,138 @@
+# Hetzner Cloud Pricing for OpenCost CSV Provider
+# Generated: 2026-02-26T11:39:02.783509
+# Source: Hetzner Cloud API (https://api.hetzner.cloud/v1/pricing)
+# Prices in EUR per hour (net, excluding VAT)
+#
+EndTimestamp,InstanceID,Region,AssetClass,InstanceIDField,InstanceType,MarketPriceHourly,Version
+,cpx11,ash,node,metadata.labels.node.kubernetes.io/instance-type,cpx11,0.007200,2026.02
+,cpx11,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx11,0.006300,2026.02
+,cpx11,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx11,0.006300,2026.02
+,cpx11,hil,node,metadata.labels.node.kubernetes.io/instance-type,cpx11,0.007200,2026.02
+,cpx11,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx11,0.006300,2026.02
+,cpx11,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx11,0.011900,2026.02
+,cpx21,ash,node,metadata.labels.node.kubernetes.io/instance-type,cpx21,0.014500,2026.02
+,cpx21,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx21,0.011200,2026.02
+,cpx21,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx21,0.011200,2026.02
+,cpx21,hil,node,metadata.labels.node.kubernetes.io/instance-type,cpx21,0.014500,2026.02
+,cpx21,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx21,0.011200,2026.02
+,cpx21,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx21,0.022600,2026.02
+,cpx31,ash,node,metadata.labels.node.kubernetes.io/instance-type,cpx31,0.025700,2026.02
+,cpx31,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx31,0.021100,2026.02
+,cpx31,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx31,0.021100,2026.02
+,cpx31,hil,node,metadata.labels.node.kubernetes.io/instance-type,cpx31,0.025700,2026.02
+,cpx31,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx31,0.021100,2026.02
+,cpx31,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx31,0.039700,2026.02
+,cpx41,ash,node,metadata.labels.node.kubernetes.io/instance-type,cpx41,0.048100,2026.02
+,cpx41,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx41,0.040900,2026.02
+,cpx41,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx41,0.040900,2026.02
+,cpx41,hil,node,metadata.labels.node.kubernetes.io/instance-type,cpx41,0.048100,2026.02
+,cpx41,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx41,0.040900,2026.02
+,cpx41,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx41,0.068300,2026.02
+,cpx51,ash,node,metadata.labels.node.kubernetes.io/instance-type,cpx51,0.096200,2026.02
+,cpx51,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx51,0.087200,2026.02
+,cpx51,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx51,0.087200,2026.02
+,cpx51,hil,node,metadata.labels.node.kubernetes.io/instance-type,cpx51,0.096200,2026.02
+,cpx51,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx51,0.087200,2026.02
+,cpx51,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx51,0.123200,2026.02
+,cax11,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cax11,0.005300,2026.02
+,cax11,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cax11,0.005300,2026.02
+,cax11,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cax11,0.005300,2026.02
+,cax21,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cax21,0.009600,2026.02
+,cax21,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cax21,0.009600,2026.02
+,cax21,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cax21,0.009600,2026.02
+,cax31,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cax31,0.019200,2026.02
+,cax31,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cax31,0.019200,2026.02
+,cax31,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cax31,0.019200,2026.02
+,cax41,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cax41,0.038400,2026.02
+,cax41,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cax41,0.038400,2026.02
+,cax41,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cax41,0.038400,2026.02
+,ccx13,ash,node,metadata.labels.node.kubernetes.io/instance-type,ccx13,0.020900,2026.02
+,ccx13,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,ccx13,0.019200,2026.02
+,ccx13,hel1,node,metadata.labels.node.kubernetes.io/instance-type,ccx13,0.019200,2026.02
+,ccx13,hil,node,metadata.labels.node.kubernetes.io/instance-type,ccx13,0.020900,2026.02
+,ccx13,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,ccx13,0.019200,2026.02
+,ccx13,sin,node,metadata.labels.node.kubernetes.io/instance-type,ccx13,0.033700,2026.02
+,ccx23,ash,node,metadata.labels.node.kubernetes.io/instance-type,ccx23,0.041700,2026.02
+,ccx23,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,ccx23,0.038400,2026.02
+,ccx23,hel1,node,metadata.labels.node.kubernetes.io/instance-type,ccx23,0.038400,2026.02
+,ccx23,hil,node,metadata.labels.node.kubernetes.io/instance-type,ccx23,0.041700,2026.02
+,ccx23,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,ccx23,0.038400,2026.02
+,ccx23,sin,node,metadata.labels.node.kubernetes.io/instance-type,ccx23,0.063100,2026.02
+,ccx33,ash,node,metadata.labels.node.kubernetes.io/instance-type,ccx33,0.080200,2026.02
+,ccx33,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,ccx33,0.076900,2026.02
+,ccx33,hel1,node,metadata.labels.node.kubernetes.io/instance-type,ccx33,0.076900,2026.02
+,ccx33,hil,node,metadata.labels.node.kubernetes.io/instance-type,ccx33,0.080200,2026.02
+,ccx33,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,ccx33,0.076900,2026.02
+,ccx33,sin,node,metadata.labels.node.kubernetes.io/instance-type,ccx33,0.119200,2026.02
+,ccx43,ash,node,metadata.labels.node.kubernetes.io/instance-type,ccx43,0.160300,2026.02
+,ccx43,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,ccx43,0.153800,2026.02
+,ccx43,hel1,node,metadata.labels.node.kubernetes.io/instance-type,ccx43,0.153800,2026.02
+,ccx43,hil,node,metadata.labels.node.kubernetes.io/instance-type,ccx43,0.160300,2026.02
+,ccx43,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,ccx43,0.153800,2026.02
+,ccx43,sin,node,metadata.labels.node.kubernetes.io/instance-type,ccx43,0.219600,2026.02
+,ccx53,ash,node,metadata.labels.node.kubernetes.io/instance-type,ccx53,0.320500,2026.02
+,ccx53,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,ccx53,0.307700,2026.02
+,ccx53,hel1,node,metadata.labels.node.kubernetes.io/instance-type,ccx53,0.307700,2026.02
+,ccx53,hil,node,metadata.labels.node.kubernetes.io/instance-type,ccx53,0.320500,2026.02
+,ccx53,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,ccx53,0.307700,2026.02
+,ccx53,sin,node,metadata.labels.node.kubernetes.io/instance-type,ccx53,0.471200,2026.02
+,ccx63,ash,node,metadata.labels.node.kubernetes.io/instance-type,ccx63,0.480800,2026.02
+,ccx63,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,ccx63,0.461500,2026.02
+,ccx63,hel1,node,metadata.labels.node.kubernetes.io/instance-type,ccx63,0.461500,2026.02
+,ccx63,hil,node,metadata.labels.node.kubernetes.io/instance-type,ccx63,0.480800,2026.02
+,ccx63,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,ccx63,0.461500,2026.02
+,ccx63,sin,node,metadata.labels.node.kubernetes.io/instance-type,ccx63,0.772400,2026.02
+,cpx12,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx12,0.009600,2026.02
+,cpx22,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx22,0.009600,2026.02
+,cpx22,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx22,0.009600,2026.02
+,cpx22,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx22,0.009600,2026.02
+,cpx22,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx22,0.019200,2026.02
+,cpx32,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx32,0.016800,2026.02
+,cpx32,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx32,0.016800,2026.02
+,cpx32,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx32,0.016800,2026.02
+,cpx32,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx32,0.040000,2026.02
+,cpx42,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx42,0.031200,2026.02
+,cpx42,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx42,0.031200,2026.02
+,cpx42,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx42,0.031200,2026.02
+,cpx42,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx42,0.068900,2026.02
+,cpx52,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx52,0.044900,2026.02
+,cpx52,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx52,0.044900,2026.02
+,cpx52,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx52,0.044900,2026.02
+,cpx52,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx52,0.096100,2026.02
+,cpx62,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cpx62,0.061700,2026.02
+,cpx62,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cpx62,0.061700,2026.02
+,cpx62,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cpx62,0.061700,2026.02
+,cpx62,sin,node,metadata.labels.node.kubernetes.io/instance-type,cpx62,0.123400,2026.02
+,cx23,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cx23,0.004800,2026.02
+,cx23,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cx23,0.004800,2026.02
+,cx23,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cx23,0.004800,2026.02
+,cx33,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cx33,0.008000,2026.02
+,cx33,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cx33,0.008000,2026.02
+,cx33,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cx33,0.008000,2026.02
+,cx43,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cx43,0.014400,2026.02
+,cx43,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cx43,0.014400,2026.02
+,cx43,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cx43,0.014400,2026.02
+,cx53,fsn1,node,metadata.labels.node.kubernetes.io/instance-type,cx53,0.027200,2026.02
+,cx53,hel1,node,metadata.labels.node.kubernetes.io/instance-type,cx53,0.027200,2026.02
+,cx53,nbg1,node,metadata.labels.node.kubernetes.io/instance-type,cx53,0.027200,2026.02
+,lb11,fsn1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb11,0.008800,2026.02
+,lb11,nbg1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb11,0.008800,2026.02
+,lb11,hel1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb11,0.008800,2026.02
+,lb11,ash,node,metadata.labels.load-balancer.hetzner.cloud/type,lb11,0.008800,2026.02
+,lb11,hil,node,metadata.labels.load-balancer.hetzner.cloud/type,lb11,0.008800,2026.02
+,lb11,sin,node,metadata.labels.load-balancer.hetzner.cloud/type,lb11,0.008800,2026.02
+,lb21,fsn1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb21,0.025300,2026.02
+,lb21,nbg1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb21,0.025300,2026.02
+,lb21,hel1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb21,0.025300,2026.02
+,lb21,ash,node,metadata.labels.load-balancer.hetzner.cloud/type,lb21,0.025300,2026.02
+,lb21,hil,node,metadata.labels.load-balancer.hetzner.cloud/type,lb21,0.025300,2026.02
+,lb21,sin,node,metadata.labels.load-balancer.hetzner.cloud/type,lb21,0.025300,2026.02
+,lb31,fsn1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb31,0.049500,2026.02
+,lb31,nbg1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb31,0.049500,2026.02
+,lb31,hel1,node,metadata.labels.load-balancer.hetzner.cloud/type,lb31,0.049500,2026.02
+,lb31,ash,node,metadata.labels.load-balancer.hetzner.cloud/type,lb31,0.049500,2026.02
+,lb31,hil,node,metadata.labels.load-balancer.hetzner.cloud/type,lb31,0.049500,2026.02
+,lb31,sin,node,metadata.labels.load-balancer.hetzner.cloud/type,lb31,0.049500,2026.02
+,ssd,fsn1,pv,spec.storageClassName,hcloud-volumes,0.00006027,2026.02
+,ssd,nbg1,pv,spec.storageClassName,hcloud-volumes,0.00006027,2026.02
+,ssd,hel1,pv,spec.storageClassName,hcloud-volumes,0.00006027,2026.02
diff --git a/scripts/hetzner_pricing_generator.py b/scripts/hetzner_pricing_generator.py
@@ -0,0 +1,230 @@
+#!/usr/bin/env python3
+"""
+Hetzner Cloud Pricing CSV Generator for OpenCost
+
+This script fetches current pricing from the Hetzner Cloud API and generates
+an OpenCost-compatible CSV file for cost tracking.
+
+Requirements:
+ pip install requests
+
+Usage:
+ # Set your Hetzner Cloud API token
+ export HCLOUD_TOKEN="your-api-token"
+
+ # Run the script
+ python3 hetzner_pricing_generator.py
+
+ # Output will be written to hetzner_pricing.csv
+
+API Documentation:
+ https://docs.hetzner.cloud/reference/cloud#pricing
+"""
+
+import csv
+import os
+import sys
+from datetime import datetime
+from typing import Any
+
+try:
+ import requests
+except ImportError:
+ print("Error: 'requests' library is required. Install with: pip install requests")
+ sys.exit(1)
+
+
+HETZNER_API_URL = "https://api.hetzner.cloud/v1"
+
+# Hetzner regions
+REGIONS = ["fsn1", "nbg1", "hel1", "ash", "hil", "sin"]
+
+# EU regions (pricing in the CSV will use EU rates by default)
+EU_REGIONS = ["fsn1", "nbg1", "hel1"]
+
+
+def get_api_token() -> str:
+ """Get Hetzner Cloud API token from environment."""
+ token = os.environ.get("HCLOUD_TOKEN")
+ if not token:
+ print("Error: HCLOUD_TOKEN environment variable not set")
+ print("Get your token from: https://console.hetzner.cloud/projects/*/security/tokens")
+ sys.exit(1)
+ return token
+
+
+def fetch_pricing(token: str) -> dict[str, Any]:
+ """Fetch pricing data from Hetzner Cloud API."""
+ headers = {"Authorization": f"Bearer {token}"}
+ response = requests.get(f"{HETZNER_API_URL}/pricing", headers=headers)
+
+ if response.status_code != 200:
+ print(f"Error fetching pricing: {response.status_code}")
+ print(response.text)
+ sys.exit(1)
+
+ return response.json()
+
+
+def fetch_server_types(token: str) -> dict[str, Any]:
+ """Fetch server types from Hetzner Cloud API."""
+ headers = {"Authorization": f"Bearer {token}"}
+ response = requests.get(f"{HETZNER_API_URL}/server_types", headers=headers)
+
+ if response.status_code != 200:
+ print(f"Error fetching server types: {response.status_code}")
+ print(response.text)
+ sys.exit(1)
+
+ return response.json()
+
+
+def fetch_load_balancer_types(token: str) -> dict[str, Any]:
+ """Fetch load balancer types from Hetzner Cloud API."""
+ headers = {"Authorization": f"Bearer {token}"}
+ response = requests.get(f"{HETZNER_API_URL}/load_balancer_types", headers=headers)
+
+ if response.status_code != 200:
+ print(f"Error fetching load balancer types: {response.status_code}")
+ print(response.text)
+ sys.exit(1)
+
+ return response.json()
+
+
+def parse_hourly_price(price_str: str) -> float:
+ """Parse price string to float."""
+ try:
+ return float(price_str)
+ except (ValueError, TypeError):
+ return 0.0
+
+
+def generate_csv(output_file: str = "hetzner_pricing.csv"):
+ """Generate OpenCost-compatible CSV from Hetzner API data."""
+ token = get_api_token()
+
+ print("Fetching pricing data from Hetzner Cloud API...")
+ pricing_data = fetch_pricing(token)
+ server_types_data = fetch_server_types(token)
+ lb_types_data = fetch_load_balancer_types(token)
+
+ version = datetime.now().strftime("%Y.%m")
+ rows = []
+
+ # Header
+ header = [
+ "EndTimestamp", "InstanceID", "Region", "AssetClass",
+ "InstanceIDField", "InstanceType", "MarketPriceHourly", "Version"
+ ]
+
+ # Process server types
+ print("Processing server types...")
+ for server_type in server_types_data.get("server_types", []):
+ name = server_type["name"]
+
+ # Get pricing for each location
+ for price_info in server_type.get("prices", []):
+ location = price_info.get("location")
+ hourly_price = parse_hourly_price(
+ price_info.get("price_hourly", {}).get("net", "0")
+ )
+
+ if hourly_price > 0:
+ rows.append([
+ "", # EndTimestamp
+ name, # InstanceID
+ location, # Region
+ "node", # AssetClass
+ "metadata.labels.node.kubernetes.io/instance-type", # InstanceIDField
+ name, # InstanceType
+ f"{hourly_price:.6f}", # MarketPriceHourly
+ version # Version
+ ])
+
+ # Process load balancer types
+ print("Processing load balancer types...")
+ for lb_type in lb_types_data.get("load_balancer_types", []):
+ name = lb_type["name"]
+
+ for price_info in lb_type.get("prices", []):
+ location = price_info.get("location")
+ hourly_price = parse_hourly_price(
+ price_info.get("price_hourly", {}).get("net", "0")
+ )
+
+ if hourly_price > 0:
+ rows.append([
+ "", # EndTimestamp
+ name, # InstanceID
+ location, # Region
+ "node", # AssetClass (using node for LBs to track costs)
+ "metadata.labels.load-balancer.hetzner.cloud/type", # InstanceIDField
+ name, # InstanceType
+ f"{hourly_price:.6f}", # MarketPriceHourly
+ version # Version
+ ])
+
+ # Process volume pricing
+ print("Processing volume pricing...")
+ pricing = pricing_data.get("pricing", {})
+ volume_pricing = pricing.get("volume", {})
+
+ # Volume pricing is per GB per month, convert to hourly
+ volume_monthly = parse_hourly_price(
+ volume_pricing.get("price_per_gb_month", {}).get("net", "0.052")
+ )
+ volume_hourly = volume_monthly / 730 # Average hours per month
+
+ for region in EU_REGIONS:
+ rows.append([
+ "", # EndTimestamp
+ "ssd", # InstanceID
+ region, # Region
+ "pv", # AssetClass
+ "spec.storageClassName", # InstanceIDField
+ "hcloud-volumes", # InstanceType
+ f"{volume_hourly:.8f}", # MarketPriceHourly
+ version # Version
+ ])
+
+ # Write CSV
+ print(f"Writing {len(rows)} pricing entries to {output_file}...")
+ with open(output_file, "w", newline="") as f:
+ # Write header comment
+ f.write("# Hetzner Cloud Pricing for OpenCost CSV Provider\n")
+ f.write(f"# Generated: {datetime.now().isoformat()}\n")
+ f.write("# Source: Hetzner Cloud API (https://api.hetzner.cloud/v1/pricing)\n")
+ f.write("# Prices in EUR per hour (net, excluding VAT)\n")
+ f.write("#\n")
+
+ writer = csv.writer(f)
+ writer.writerow(header)
+ writer.writerows(rows)
+
+ print(f"Done! Generated {output_file} with {len(rows)} pricing entries.")
+ print("\nTo use with OpenCost:")
+ print(" 1. Copy the CSV to your OpenCost deployment")
+ print(" 2. Set USE_CSV_PROVIDER=true")
+ print(" 3. Set CSV_PATH to the file location")
+
+
+def main():
+ """Main entry point."""
+ import argparse
+
+ parser = argparse.ArgumentParser(
+ description="Generate OpenCost-compatible pricing CSV from Hetzner Cloud API"
+ )
+ parser.add_argument(
+ "-o", "--output",
+ default="hetzner_pricing.csv",
+ help="Output CSV file path (default: hetzner_pricing.csv)"
+ )
+
+ args = parser.parse_args()
+ generate_csv(args.output)
+
+
+if __name__ == "__main__":
+ main()