diff --git a/description_templates/_dbaas_usage.gotmpl b/description_templates/_dbaas_usage.gotmpl new file mode 100644 index 0000000..cac3de2 --- /dev/null +++ b/description_templates/_dbaas_usage.gotmpl @@ -0,0 +1,10 @@ +Exoscale Kafka + +{{- define "_dbaas_usage" -}} +{{ $keySeg := splitList ":" .Source -}} +{{ if ge (len $keySeg) 5 -}} +Plan: {{ index $keySeg 4 }} +{{ end -}} +Qty: {{.Quantity | printf "%.0f"}} Instance-Hours +Unit Price: CHF {{.PricePerUnit | printf "%.8f"}} / {{.Unit}} / Hour +{{ end -}} diff --git a/description_templates/golden/kafka:exoscale.txt b/description_templates/golden/kafka:exoscale.txt new file mode 100755 index 0000000..5c082c6 --- /dev/null +++ b/description_templates/golden/kafka:exoscale.txt @@ -0,0 +1,4 @@ +Exoscale Kafka + +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden/kafka:exoscale:*:*:business-16.txt b/description_templates/golden/kafka:exoscale:*:*:business-16.txt new file mode 100755 index 0000000..b9a5381 --- /dev/null +++ b/description_templates/golden/kafka:exoscale:*:*:business-16.txt @@ -0,0 +1,5 @@ +Exoscale Kafka + +Plan: business-16 +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden/kafka:exoscale:*:*:premium-30x-32.txt b/description_templates/golden/kafka:exoscale:*:*:premium-30x-32.txt new file mode 100755 index 0000000..2c5ef3e --- /dev/null +++ b/description_templates/golden/kafka:exoscale:*:*:premium-30x-32.txt @@ -0,0 +1,5 @@ +Exoscale Kafka + +Plan: premium-30x-32 +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden/mysql:exoscale.txt b/description_templates/golden/mysql:exoscale.txt new file mode 100755 index 0000000..c39b350 --- /dev/null +++ b/description_templates/golden/mysql:exoscale.txt @@ -0,0 +1,4 @@ +Exoscale MySQL + +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden/mysql:exoscale:*:*:business-225.txt b/description_templates/golden/mysql:exoscale:*:*:business-225.txt new file mode 100755 index 0000000..ad57526 --- /dev/null +++ b/description_templates/golden/mysql:exoscale:*:*:business-225.txt @@ -0,0 +1,5 @@ +Exoscale MySQL + +Plan: business-225 +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden/mysql:exoscale:*:*:startup-16.txt b/description_templates/golden/mysql:exoscale:*:*:startup-16.txt new file mode 100755 index 0000000..c52a925 --- /dev/null +++ b/description_templates/golden/mysql:exoscale:*:*:startup-16.txt @@ -0,0 +1,5 @@ +Exoscale MySQL + +Plan: startup-16 +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden/opensearch:exoscale.txt b/description_templates/golden/opensearch:exoscale.txt new file mode 100755 index 0000000..1c8a1f3 --- /dev/null +++ b/description_templates/golden/opensearch:exoscale.txt @@ -0,0 +1,4 @@ +Exoscale OpenSearch + +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden/opensearch:exoscale:*:*:premium-30x-16.txt b/description_templates/golden/opensearch:exoscale:*:*:premium-30x-16.txt new file mode 100755 index 0000000..c9c4c12 --- /dev/null +++ b/description_templates/golden/opensearch:exoscale:*:*:premium-30x-16.txt @@ -0,0 +1,5 @@ +Exoscale OpenSearch + +Plan: premium-30x-16 +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden/opensearch:exoscale:*:*:startup-8.txt b/description_templates/golden/opensearch:exoscale:*:*:startup-8.txt new file mode 100755 index 0000000..ca719fa --- /dev/null +++ b/description_templates/golden/opensearch:exoscale:*:*:startup-8.txt @@ -0,0 +1,5 @@ +Exoscale OpenSearch + +Plan: startup-8 +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden/postgres:exoscale.txt b/description_templates/golden/postgres:exoscale.txt new file mode 100755 index 0000000..8709cec --- /dev/null +++ b/description_templates/golden/postgres:exoscale.txt @@ -0,0 +1,4 @@ +Exoscale PostgreSQL + +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden/postgres:exoscale:*:*:premium-32.txt b/description_templates/golden/postgres:exoscale:*:*:premium-32.txt new file mode 100755 index 0000000..3395464 --- /dev/null +++ b/description_templates/golden/postgres:exoscale:*:*:premium-32.txt @@ -0,0 +1,5 @@ +Exoscale PostgreSQL + +Plan: premium-32 +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden/postgres:exoscale:*:*:startup-8.txt b/description_templates/golden/postgres:exoscale:*:*:startup-8.txt new file mode 100755 index 0000000..03a3e2c --- /dev/null +++ b/description_templates/golden/postgres:exoscale:*:*:startup-8.txt @@ -0,0 +1,5 @@ +Exoscale PostgreSQL + +Plan: startup-8 +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden/redis:exoscale.txt b/description_templates/golden/redis:exoscale.txt new file mode 100755 index 0000000..6f9afa7 --- /dev/null +++ b/description_templates/golden/redis:exoscale.txt @@ -0,0 +1,4 @@ +Exoscale Redis + +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden/redis:exoscale:*:*:hobbyist-2.txt b/description_templates/golden/redis:exoscale:*:*:hobbyist-2.txt new file mode 100755 index 0000000..6d3363f --- /dev/null +++ b/description_templates/golden/redis:exoscale:*:*:hobbyist-2.txt @@ -0,0 +1,5 @@ +Exoscale Redis + +Plan: hobbyist-2 +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden/redis:exoscale:*:*:premium-16.txt b/description_templates/golden/redis:exoscale:*:*:premium-16.txt new file mode 100755 index 0000000..9d0dd78 --- /dev/null +++ b/description_templates/golden/redis:exoscale:*:*:premium-16.txt @@ -0,0 +1,5 @@ +Exoscale Redis + +Plan: premium-16 +Qty: 87955674 Instance-Hours +Unit Price: CHF 0.00000075 / UNIT / Hour diff --git a/description_templates/golden_test.go b/description_templates/golden_test.go index 9547d29..2e20392 100644 --- a/description_templates/golden_test.go +++ b/description_templates/golden_test.go @@ -49,6 +49,12 @@ func TestGenerateGolden(t *testing.T) { sourceKeys = append(sourceKeys, strings.TrimSuffix(name, extension)) } + sourceKeys = append(sourceKeys, "kafka:exoscale:*:*:business-16", "kafka:exoscale:*:*:premium-30x-32") + sourceKeys = append(sourceKeys, "opensearch:exoscale:*:*:startup-8", "opensearch:exoscale:*:*:premium-30x-16") + sourceKeys = append(sourceKeys, "redis:exoscale:*:*:hobbyist-2", "redis:exoscale:*:*:premium-16") + sourceKeys = append(sourceKeys, "postgres:exoscale:*:*:startup-8", "postgres:exoscale:*:*:premium-32") + sourceKeys = append(sourceKeys, "mysql:exoscale:*:*:startup-16", "mysql:exoscale:*:*:business-225") + baseItem := invoice.Item{ Description: "Long form query description", QueryName: "default_query", diff --git a/description_templates/kafka:exoscale.gotmpl b/description_templates/kafka:exoscale.gotmpl new file mode 100644 index 0000000..b538c3c --- /dev/null +++ b/description_templates/kafka:exoscale.gotmpl @@ -0,0 +1,3 @@ +Exoscale Kafka + +{{template "_dbaas_usage" . -}} diff --git a/description_templates/mysql:exoscale.gotmpl b/description_templates/mysql:exoscale.gotmpl new file mode 100644 index 0000000..d0b780a --- /dev/null +++ b/description_templates/mysql:exoscale.gotmpl @@ -0,0 +1,3 @@ +Exoscale MySQL + +{{template "_dbaas_usage" . -}} diff --git a/description_templates/opensearch:exoscale.gotmpl b/description_templates/opensearch:exoscale.gotmpl new file mode 100644 index 0000000..6e19770 --- /dev/null +++ b/description_templates/opensearch:exoscale.gotmpl @@ -0,0 +1,3 @@ +Exoscale OpenSearch + +{{template "_dbaas_usage" . -}} diff --git a/description_templates/postgres:exoscale.gotmpl b/description_templates/postgres:exoscale.gotmpl new file mode 100644 index 0000000..bcb83b1 --- /dev/null +++ b/description_templates/postgres:exoscale.gotmpl @@ -0,0 +1,3 @@ +Exoscale PostgreSQL + +{{template "_dbaas_usage" . -}} diff --git a/description_templates/redis:exoscale.gotmpl b/description_templates/redis:exoscale.gotmpl new file mode 100644 index 0000000..e0ab9d8 --- /dev/null +++ b/description_templates/redis:exoscale.gotmpl @@ -0,0 +1,3 @@ +Exoscale Redis + +{{template "_dbaas_usage" . -}} diff --git a/invoice/desctmpl/renderer.go b/invoice/desctmpl/renderer.go index 1378044..a12ea9c 100644 --- a/invoice/desctmpl/renderer.go +++ b/invoice/desctmpl/renderer.go @@ -40,14 +40,27 @@ func ItemDescriptionTemplateRendererFromFS(fs fs.FS, extension string) (*ItemDes } // RenderItemDescription renders an item description. Uses the `.ProductRef.Source` as the key to look which template to use. +// If there are no exact matches, it will try to find templates that match a substring of the key using longest prefix match. +// Example: Source "foo:bar:buzz" will match template "foo:bar" if template "foo:bar:buzz" does not exist. func (r *ItemDescriptionTemplateRenderer) RenderItemDescription(_ context.Context, item invoice.Item) (string, error) { - key := item.ProductRef.Source - tmpl := r.root.Lookup(key + r.extension) - if tmpl == nil { - return "", fmt.Errorf("failed to find template for `ProductRef.Source=%q`%s", key, r.root.DefinedTemplates()) + tmpl, err := r.lookup(item.ProductRef.Source) + if err != nil { + return "", err } - b := &strings.Builder{} - err := r.root.ExecuteTemplate(b, item.ProductRef.Source+r.extension, item) + err = tmpl.Execute(b, item) return b.String(), err } + +func (r *ItemDescriptionTemplateRenderer) lookup(key string) (*template.Template, error) { + + segments := strings.Split(key, ":") + for i := len(segments); i > 0; i-- { + tmpl := r.root.Lookup(strings.Join(segments[:i], ":") + r.extension) + if tmpl != nil { + return tmpl, nil + } + } + + return nil, fmt.Errorf("failed to find template for `ProductRef.Source=%q`%s", key, r.root.DefinedTemplates()) +} diff --git a/invoice/desctmpl/renderer_test.go b/invoice/desctmpl/renderer_test.go index b1dbad2..40caa1c 100644 --- a/invoice/desctmpl/renderer_test.go +++ b/invoice/desctmpl/renderer_test.go @@ -19,6 +19,12 @@ func TestRenderItemDescription(t *testing.T) { "storage" + extension: &fstest.MapFile{ Data: []byte("so vieli bytesli: {{.Total}}"), }, + "kafka" + extension: &fstest.MapFile{ + Data: []byte("so kafkaesque: {{.Total}}"), + }, + "kafka:exoscale:*:*:premium-30x-33" + extension: &fstest.MapFile{ + Data: []byte("not so kafkaesque: {{.Total}}"), + }, } subject, err := desctmpl.ItemDescriptionTemplateRendererFromFS(templateFS, extension) @@ -35,24 +41,57 @@ func TestRenderItemDescription(t *testing.T) { invoice.Item{ProductRef: invoice.ProductRef{Source: "memory"}, Total: 77}, "memory: 77", require.NoError, - }, { + }, + { "storage source", invoice.Item{ProductRef: invoice.ProductRef{Source: "storage"}, Total: 99}, "so vieli bytesli: 99", require.NoError, - }, { + }, + { + "kafka source", + invoice.Item{ProductRef: invoice.ProductRef{Source: "kafka"}, Total: 11}, + "so kafkaesque: 11", + require.NoError, + }, + { + "specialized kafka source", + invoice.Item{ProductRef: invoice.ProductRef{Source: "kafka:exoscale:*:*:premium-30x-32"}, Total: 12}, + "so kafkaesque: 12", + require.NoError, + }, + { + "weird specialized kafka source", + invoice.Item{ProductRef: invoice.ProductRef{Source: "kafka:exoscale:::.a::*:*:premium-30x-32"}, Total: 15}, + "so kafkaesque: 15", + require.NoError, + }, + + { + "specialized kafka source with special template", + invoice.Item{ProductRef: invoice.ProductRef{Source: "kafka:exoscale:*:*:premium-30x-33"}, Total: 12}, + "not so kafkaesque: 12", + require.NoError, + }, + { "unknown source", invoice.Item{ProductRef: invoice.ProductRef{Source: "unknown"}, Total: 77}, "", require.Error, }, + { + "weird unknown source", + invoice.Item{ProductRef: invoice.ProductRef{Source: ":::plswhy?::unknown:::"}, Total: 77}, + "", + require.Error, + }, } for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { rendered, err := subject.RenderItemDescription(context.Background(), tc.item) - require.Equal(t, tc.expectedOut, rendered) tc.expectedErr(t, err) + require.Equal(t, tc.expectedOut, rendered) }) } }