cart and checkout
Build and Publish / BuildAndDeployAmd64 (push) Failing after 4s
Build and Publish / BuildAndDeployArm64 (push) Failing after 6s

This commit is contained in:
2026-06-27 19:49:00 +02:00
parent 492f54ff45
commit 528c59bfd3
67 changed files with 3618 additions and 1031 deletions
+3 -1
View File
@@ -24,7 +24,9 @@ func GetDiscovery() discovery.Discovery {
log.Fatalf("Error creating client: %v\n", err)
}
timeout := int64(30)
return discovery.NewK8sDiscovery(client, v1.ListOptions{
// Scope discovery to this pod's namespace so it only needs a namespaced Role
// (pods: get/list/watch), not a cluster-wide ClusterRole.
return discovery.NewK8sDiscoveryInNamespace(client, discovery.InClusterNamespace(), v1.ListOptions{
LabelSelector: "actor-pool=cart",
TimeoutSeconds: &timeout,
})
+74 -27
View File
@@ -12,19 +12,21 @@ import (
"strings"
"time"
"git.k6n.net/mats/go-cart-actor/internal/ucp"
"git.k6n.net/mats/go-cart-actor/pkg/actor"
"git.k6n.net/mats/go-cart-actor/pkg/cart"
cartmcp "git.k6n.net/mats/go-cart-actor/pkg/cart/mcp"
"git.k6n.net/mats/go-cart-actor/pkg/promotions"
"git.k6n.net/mats/go-cart-actor/internal/ucp"
"git.k6n.net/mats/go-cart-actor/pkg/proxy"
"git.k6n.net/mats/go-cart-actor/pkg/telemetry"
"git.k6n.net/mats/go-cart-actor/pkg/voucher"
"git.k6n.net/mats/go-redis-inventory/pkg/inventory"
"git.k6n.net/mats/slask-finder/pkg/messaging"
amqp "github.com/rabbitmq/amqp091-go"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
amqp "github.com/rabbitmq/amqp091-go"
"github.com/redis/go-redis/v9"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
@@ -51,13 +53,6 @@ var redisAddress = os.Getenv("REDIS_ADDRESS")
var redisPassword = os.Getenv("REDIS_PASSWORD")
// getEnv returns the value of the environment variable named by key, or def if unset/empty.
func getEnv(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
// normalizeListenAddr accepts either a bare port ("8080") or a full listen
// address (":8080", "0.0.0.0:8080") and returns a value usable by http.Server.Addr.
func normalizeListenAddr(v string) string {
@@ -67,6 +62,14 @@ func normalizeListenAddr(v string) string {
return ":" + v
}
// getEnv returns the value of the environment variable named by key, or def if unset/empty.
func getEnv(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
// loadUCPCartSigner loads the UCP ECDSA signing key from the path specified in
// the UCP_SIGNING_KEY_PATH environment variable. Returns nil if unset or unreadable.
func loadUCPCartSigner() *ucp.SigningConfig {
@@ -144,32 +147,76 @@ func main() {
//inventoryPubSub := actor.NewPubSub[inventory.InventoryChange]()
// rdb := redis.NewClient(&redis.Options{
// Addr: redisAddress,
// Password: redisPassword,
// DB: 0,
// })
// inventoryService, err := inventory.NewRedisInventoryService(rdb)
// if err != nil {
// log.Fatalf("Error creating inventory service: %v\n", err)
// }
rdb := redis.NewClient(&redis.Options{
Addr: redisAddress,
Password: redisPassword,
DB: 0,
})
inventoryService, err := inventory.NewRedisInventoryService(rdb)
if err != nil {
log.Fatalf("Error creating inventory service: %v\n", err)
}
_ = inventoryService
// inventoryReservationService, err := inventory.NewRedisCartReservationService(rdb)
// if err != nil {
// log.Fatalf("Error creating inventory reservation service: %v\n", err)
// }
inventoryReservationService, err := inventory.NewRedisCartReservationService(rdb)
if err != nil {
log.Fatalf("Error creating inventory reservation service: %v\n", err)
}
reg := cart.NewCartMultationRegistry(cart.NewCartMutationContext(nil))
reg := cart.NewCartMultationRegistry(cart.NewCartMutationContext(inventoryReservationService))
reg.RegisterProcessor(
actor.NewMutationProcessor(func(ctx context.Context, g *cart.CartGrain) error {
_, span := tracer.Start(ctx, "Totals and promotions")
defer span.End()
// Clear bypass flags first
for _, v := range g.Vouchers {
if v != nil {
v.BypassedByPromotions = false
}
}
g.UpdateTotals()
// Evaluate active promotions against the freshly-totalled cart and apply
// any matched actions (e.g. the Volymrabatt volume discount), which adjust
// TotalPrice/TotalDiscount on top of line items and vouchers.
promotionCtx := promotions.NewContextFromCart(g, promotions.WithNow(time.Now()))
results, _ := promotionService.EvaluateAll(promotionStore.Snapshot(), promotionCtx)
// Check if any qualified promotion rule has a coupon_code condition matching our vouchers
hasBypassed := false
for _, res := range results {
if res.Applicable {
promotions.WalkConditions(res.Rule.Conditions, func(c promotions.Condition) bool {
if bc, ok := c.(promotions.BaseCondition); ok && bc.Type == promotions.CondCouponCode {
var codes []string
if s, ok := bc.Value.AsString(); ok {
codes = append(codes, strings.ToLower(s))
} else if arr, ok := bc.Value.AsStringSlice(); ok {
for _, s := range arr {
codes = append(codes, strings.ToLower(s))
}
}
for _, code := range codes {
for _, v := range g.Vouchers {
if v != nil && strings.ToLower(v.Code) == code {
if !v.BypassedByPromotions {
v.BypassedByPromotions = true
hasBypassed = true
}
}
}
}
}
return true
})
}
}
if hasBypassed {
// Re-evaluate with bypassed vouchers
g.UpdateTotals()
promotionCtx = promotions.NewContextFromCart(g, promotions.WithNow(time.Now()))
results, _ = promotionService.EvaluateAll(promotionStore.Snapshot(), promotionCtx)
}
// ApplyResults applies qualifying actions in priority order and records
// every effect — both applied discounts and pending "spend X more for ..."
// nudges with their progress — in g.AppliedPromotions.
@@ -287,7 +334,7 @@ func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
otelShutdown, err := setupOTelSDK(ctx)
otelShutdown, err := telemetry.SetupOTelSDK(ctx)
if err != nil {
log.Fatalf("Unable to start otel %v", err)
}
-117
View File
@@ -1,117 +0,0 @@
package main
import (
"context"
"errors"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/log/global"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/trace"
)
// setupOTelSDK bootstraps the OpenTelemetry pipeline.
// If it does not return an error, make sure to call shutdown for proper cleanup.
func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) {
var shutdownFuncs []func(context.Context) error
var err error
// shutdown calls cleanup functions registered via shutdownFuncs.
// The errors from the calls are joined.
// Each registered cleanup will be invoked once.
shutdown := func(ctx context.Context) error {
var err error
for _, fn := range shutdownFuncs {
err = errors.Join(err, fn(ctx))
}
shutdownFuncs = nil
return err
}
// handleErr calls shutdown for cleanup and makes sure that all errors are returned.
handleErr := func(inErr error) {
err = errors.Join(inErr, shutdown(ctx))
}
// Set up propagator.
prop := newPropagator()
otel.SetTextMapPropagator(prop)
// Set up trace provider.
tracerProvider, err := newTracerProvider()
if err != nil {
handleErr(err)
return shutdown, err
}
shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown)
otel.SetTracerProvider(tracerProvider)
// Set up meter provider.
meterProvider, err := newMeterProvider()
if err != nil {
handleErr(err)
return shutdown, err
}
shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown)
otel.SetMeterProvider(meterProvider)
// Set up logger provider.
loggerProvider, err := newLoggerProvider()
if err != nil {
handleErr(err)
return shutdown, err
}
shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown)
global.SetLoggerProvider(loggerProvider)
return shutdown, err
}
func newPropagator() propagation.TextMapPropagator {
return propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
)
}
func newTracerProvider() (*trace.TracerProvider, error) {
traceExporter, err := otlptracegrpc.New(context.Background())
if err != nil {
return nil, err
}
tracerProvider := trace.NewTracerProvider(
trace.WithBatcher(traceExporter,
// Default is 5s. Set to 1s for demonstrative purposes.
trace.WithBatchTimeout(time.Second)),
)
return tracerProvider, nil
}
func newMeterProvider() (*metric.MeterProvider, error) {
exporter, err := otlpmetricgrpc.New(context.Background())
if err != nil {
return nil, err
}
provider := metric.NewMeterProvider(metric.WithReader(metric.NewPeriodicReader(exporter)))
return provider, nil
}
func newLoggerProvider() (*log.LoggerProvider, error) {
logExporter, err := otlploggrpc.New(context.Background())
if err != nil {
return nil, err
}
loggerProvider := log.NewLoggerProvider(
log.WithProcessor(log.NewBatchProcessor(logExporter)),
)
return loggerProvider, nil
}
-3
View File
@@ -17,7 +17,6 @@ import (
"git.k6n.net/mats/go-cart-actor/pkg/voucher"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"go.opentelemetry.io/contrib/bridges/otelslog"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
@@ -399,7 +398,6 @@ func (s *PoolServer) ProxyHandler(fn func(w http.ResponseWriter, r *http.Request
span.SetAttributes(attribute.String("cartid", cartId.String()))
hostAttr := attribute.String("other host", ownerHost.Name())
span.SetAttributes(hostAttr)
logger.InfoContext(ctx, "cart proxyed", "result", ownerHost.Name())
proxyCalls.Add(ctx, 1, metric.WithAttributes(hostAttr))
handled, err := ownerHost.Proxy(uint64(cartId), w, r, nil)
@@ -419,7 +417,6 @@ func (s *PoolServer) ProxyHandler(fn func(w http.ResponseWriter, r *http.Request
var (
tracer = otel.Tracer(name)
meter = otel.Meter(name)
logger = otelslog.NewLogger(name)
proxyCalls metric.Int64Counter
)