Skip to content

Commit

Permalink
Merge pull request #270 from vshn/collabora-billing
Browse files Browse the repository at this point in the history
Enable nextcloud office billing
  • Loading branch information
zugao authored Dec 3, 2024
2 parents bcfea90 + 8c4e9ec commit 237beb2
Show file tree
Hide file tree
Showing 5 changed files with 529 additions and 31 deletions.
1 change: 1 addition & 0 deletions apis/vshn/v1/vshn_nextcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ func (v *VSHNNextcloud) SetInstanceNamespaceStatus() {
v.Status.InstanceNamespace = v.GetInstanceNamespace()
}

// CollaboraSpec defines the desired state of a Collabora instance.
type CollaboraSpec struct {
// Enabled enables the Collabora integration. It will autoconfigure the Collabora server URL in Your Nextcloud instance.
//+kubebuilder:default=false
Expand Down
74 changes: 43 additions & 31 deletions pkg/comp-functions/functions/common/billing.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
package common

import (
"bytes"
"context"
_ "embed"
"fmt"
"text/template"

xfnproto "github.com/crossplane/function-sdk-go/proto/v1beta1"
v1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"github.com/vshn/appcat/v4/pkg/comp-functions/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
controllerruntime "sigs.k8s.io/controller-runtime"
)

var rawExpr = "vector({{.}})"
// ServiceAddOns describes an addOn for a services with necessary data for billing
type ServiceAddOns struct {
Name string
Instances int
}

// CreateBillingRecord creates a new prometheus rule per each instance namespace
// The rule is skipped for any secondary service such as postgresql instance for nextcloud
// The skipping is based on whether label appuio.io/billing-name is set or not on instance namespace
func CreateBillingRecord(ctx context.Context, svc *runtime.ServiceRuntime, comp InfoGetter) *xfnproto.Result {
func CreateBillingRecord(ctx context.Context, svc *runtime.ServiceRuntime, comp InfoGetter, addOns ...ServiceAddOns) *xfnproto.Result {
log := controllerruntime.LoggerFrom(ctx)
log.Info("Enabling billing for service", "service", comp.GetName())

Expand All @@ -28,10 +29,7 @@ func CreateBillingRecord(ctx context.Context, svc *runtime.ServiceRuntime, comp
return nil
}

expr, err := getExprFromTemplate(comp.GetInstances())
if err != nil {
runtime.NewWarningResult(fmt.Sprintf("cannot add billing to service %s", comp.GetName()))
}
expr := getVectorExpression(comp.GetInstances())

org, err := getOrg(comp.GetName(), svc)
if err != nil {
Expand All @@ -55,20 +53,31 @@ func CreateBillingRecord(ctx context.Context, svc *runtime.ServiceRuntime, comp
log.Info("secondary service, skipping billing", "service", comp.GetName())
return runtime.NewNormalResult(fmt.Sprintf("billing disabled for instance %s", comp.GetName()))
}
rg := v1.RuleGroup{
Name: "appcat-metering-rules",
Rules: []v1.Rule{
{
Record: "appcat:metering",
Expr: intstr.FromString(expr),
Labels: getLabels(svc, comp, org, ""),
},
},
}

for _, addOn := range addOns {
log.Info("Adding billing addOn for service", "service", comp.GetName(), "addOn", addOn.Name)
exprAddOn := getVectorExpression(addOn.Instances)
rg.Rules = append(rg.Rules, v1.Rule{
Record: "appcat:metering",
Expr: intstr.FromString(exprAddOn),
Labels: getLabels(svc, comp, org, addOn.Name),
})
}

p := &v1.PrometheusRule{
Spec: v1.PrometheusRuleSpec{
Groups: []v1.RuleGroup{
{
Name: "appcat-metering-rules",
Rules: []v1.Rule{
{
Record: "appcat:metering",
Expr: intstr.FromString(expr),
Labels: getLabels(org, comp, svc),
},
},
},
rg,
},
},
}
Expand All @@ -86,12 +95,16 @@ func CreateBillingRecord(ctx context.Context, svc *runtime.ServiceRuntime, comp
return runtime.NewNormalResult(fmt.Sprintf("billing enabled for instance %s", comp.GetName()))
}

func getLabels(org string, comp InfoGetter, svc *runtime.ServiceRuntime) map[string]string {
func getLabels(svc *runtime.ServiceRuntime, comp InfoGetter, org, addOnName string) map[string]string {
b, err := getBillingNameWithAddOn(comp.GetBillingName(), addOnName)
if err != nil {
panic(fmt.Errorf("set billing name for service %s: %v", comp.GetServiceName(), err))
}
labels := map[string]string{
"label_appcat_vshn_io_claim_name": comp.GetClaimName(),
"label_appcat_vshn_io_claim_namespace": comp.GetClaimNamespace(),
"label_appcat_vshn_io_sla": comp.GetSLA(),
"label_appuio_io_billing_name": comp.GetBillingName(),
"label_appuio_io_billing_name": b,
"label_appuio_io_organization": org,
}

Expand All @@ -103,17 +116,16 @@ func getLabels(org string, comp InfoGetter, svc *runtime.ServiceRuntime) map[str
return labels
}

func getExprFromTemplate(i int) (string, error) {
var buf bytes.Buffer
tmpl, err := template.New("billing").Parse(rawExpr)
if err != nil {
return "", err
func getBillingNameWithAddOn(billingName, addOn string) (string, error) {
if billingName == "" {
return "", fmt.Errorf("billing name is empty")
}

err = tmpl.Execute(&buf, i)
if err != nil {
return "", err
if addOn == "" {
return billingName, nil
}
return fmt.Sprintf("%s-%s", billingName, addOn), nil
}

return buf.String(), err
func getVectorExpression(i int) string {
return fmt.Sprintf("vector(%d)", i)
}
Loading

0 comments on commit 237beb2

Please sign in to comment.