mirror of
https://github.com/hanzo-fi/stack.git
synced 2026-10-30 20:34:43 +00:00
433 lines
12 KiB
Go
433 lines
12 KiB
Go
package configurations
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"github.com/formancehq/operator/api/stack.formance.com/v1beta3"
|
|
. "github.com/formancehq/operator/internal/core"
|
|
"github.com/formancehq/operator/internal/resources/settings"
|
|
. "github.com/formancehq/stack/libs/go-libs/collectionutils"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
)
|
|
|
|
// +kubebuilder:rbac:groups=stack.formance.com,resources=configurations,verbs=get;list;watch;create;update;patch;delete
|
|
// +kubebuilder:rbac:groups=stack.formance.com,resources=configurations/status,verbs=get;update;patch
|
|
// +kubebuilder:rbac:groups=stack.formance.com,resources=configurations/finalizers,verbs=update
|
|
|
|
func Reconcile(ctx Context, configuration *v1beta3.Configuration) error {
|
|
|
|
stacks := &v1beta3.StackList{}
|
|
if err := ctx.GetClient().List(ctx, stacks, client.MatchingFields{
|
|
".spec.seed": configuration.Name,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
stackNames := Map(stacks.Items, func(from v1beta3.Stack) string {
|
|
return from.GetName()
|
|
})
|
|
|
|
type resourceRequirementDescriptor struct {
|
|
requirements *v1beta3.ResourceProperties
|
|
deployment string
|
|
}
|
|
for _, cfg := range []resourceRequirementDescriptor{
|
|
{
|
|
requirements: configuration.Spec.Services.Ledger.ResourceProperties,
|
|
deployment: "ledger",
|
|
},
|
|
{
|
|
requirements: configuration.Spec.Services.Payments.ResourceProperties,
|
|
deployment: "payments",
|
|
},
|
|
{
|
|
requirements: configuration.Spec.Services.Orchestration.ResourceProperties,
|
|
deployment: "orchestration",
|
|
},
|
|
{
|
|
requirements: configuration.Spec.Services.Auth.ResourceProperties,
|
|
deployment: "auth",
|
|
},
|
|
{
|
|
requirements: configuration.Spec.Services.Webhooks.ResourceProperties,
|
|
deployment: "webhooks",
|
|
},
|
|
{
|
|
requirements: configuration.Spec.Services.Reconciliation.ResourceProperties,
|
|
deployment: "reconciliation",
|
|
},
|
|
{
|
|
requirements: configuration.Spec.Services.Gateway.ResourceProperties,
|
|
deployment: "gateway",
|
|
},
|
|
{
|
|
requirements: configuration.Spec.Services.Wallets.ResourceProperties,
|
|
deployment: "wallets",
|
|
},
|
|
{
|
|
requirements: configuration.Spec.Services.Stargate.ResourceProperties,
|
|
deployment: "stargate",
|
|
},
|
|
{
|
|
requirements: configuration.Spec.Services.Search.SearchResourceProperties,
|
|
deployment: "search",
|
|
},
|
|
{
|
|
requirements: configuration.Spec.Services.Search.BenthosResourceProperties,
|
|
deployment: "benthos",
|
|
},
|
|
} {
|
|
if cfg.requirements == nil {
|
|
continue
|
|
}
|
|
var computeResourceList = func(resource *v1beta3.Resource) string {
|
|
if resource == nil {
|
|
return ""
|
|
}
|
|
limits := ""
|
|
if resource.Cpu != "" {
|
|
limits = limits + "cpu=" + resource.Cpu
|
|
}
|
|
if resource.Memory != "" {
|
|
if limits != "" {
|
|
limits = limits + ","
|
|
}
|
|
limits = limits + "memory=" + resource.Memory
|
|
}
|
|
return limits
|
|
}
|
|
|
|
if limits := computeResourceList(cfg.requirements.Limits); limits != "" {
|
|
settingName := fmt.Sprintf("%s-%s-resource-limits", configuration.Name, cfg.deployment)
|
|
settingKey := fmt.Sprintf("applications.%s.containers.*.resource-requirements.limits", cfg.deployment)
|
|
|
|
_, err := settings.CreateOrUpdate(ctx, settingName, settingKey, limits, stackNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if requests := computeResourceList(cfg.requirements.Request); requests != "" {
|
|
settingName := fmt.Sprintf("%s-%s-resource-requests", configuration.Name, cfg.deployment)
|
|
settingKey := fmt.Sprintf("applications.%s.containers.*.resource-requirements.requests", cfg.deployment)
|
|
|
|
_, err := settings.CreateOrUpdate(ctx, settingName, settingKey, requests, stackNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
type databaseDescriptor struct {
|
|
config v1beta3.PostgresConfig
|
|
name string
|
|
}
|
|
|
|
for _, cfg := range []databaseDescriptor{
|
|
{
|
|
config: configuration.Spec.Services.Ledger.Postgres,
|
|
name: "ledger",
|
|
},
|
|
{
|
|
config: configuration.Spec.Services.Payments.Postgres,
|
|
name: "payments",
|
|
},
|
|
{
|
|
config: configuration.Spec.Services.Orchestration.Postgres,
|
|
name: "orchestration",
|
|
},
|
|
{
|
|
config: configuration.Spec.Services.Auth.Postgres,
|
|
name: "auth",
|
|
},
|
|
{
|
|
config: configuration.Spec.Services.Webhooks.Postgres,
|
|
name: "webhooks",
|
|
},
|
|
{
|
|
config: configuration.Spec.Services.Reconciliation.Postgres,
|
|
name: "reconciliation",
|
|
},
|
|
} {
|
|
basicAuth := ""
|
|
if cfg.config.Username != "" {
|
|
basicAuth = fmt.Sprintf("%s:%s@", cfg.config.Username, cfg.config.Password)
|
|
}
|
|
endpoint := fmt.Sprintf("%s:%d", cfg.config.Host, cfg.config.Port)
|
|
|
|
options := url.Values{}
|
|
if cfg.config.DisableSSLMode {
|
|
options.Set("disableSSLMode", "true")
|
|
}
|
|
if cfg.config.CredentialsFromSecret != "" {
|
|
options.Set("secret", cfg.config.CredentialsFromSecret)
|
|
}
|
|
|
|
_, err := settings.CreateOrUpdate(ctx, fmt.Sprintf("%s-%s-postgres-uri", configuration.Name, cfg.name),
|
|
fmt.Sprintf("postgres.%s.uri", cfg.name),
|
|
fmt.Sprintf("postgresql://%s%s?%s", basicAuth, endpoint, options.Encode()),
|
|
stackNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if monitoring := configuration.Spec.Monitoring; monitoring != nil {
|
|
|
|
var createSettings = func(discr string, spec *v1beta3.OtlpSpec) error {
|
|
options := url.Values{}
|
|
if spec.Insecure {
|
|
options.Set("insecure", "true")
|
|
}
|
|
dsn := fmt.Sprintf(
|
|
"%s://%s:%d?%s",
|
|
spec.Mode,
|
|
spec.Endpoint,
|
|
spec.Port,
|
|
options.Encode(),
|
|
)
|
|
|
|
_, err := settings.CreateOrUpdate(ctx, fmt.Sprintf("%s-otel-traces", configuration.Name),
|
|
fmt.Sprintf("opentelemetry.%s.dsn", discr), dsn, stackNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
if monitoring.Traces != nil && monitoring.Traces.Otlp != nil {
|
|
if err := createSettings("traces", monitoring.Traces.Otlp); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if monitoring.Metrics != nil && monitoring.Metrics.Otlp != nil {
|
|
if err := createSettings("metrics", monitoring.Metrics.Otlp); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if configuration.Spec.Broker.Kafka != nil {
|
|
options := url.Values{}
|
|
if configuration.Spec.Broker.Kafka.TLS {
|
|
options.Set("tls", "true")
|
|
}
|
|
if sasl := configuration.Spec.Broker.Kafka.SASL; sasl != nil {
|
|
options.Set("saslEnabled", "true")
|
|
if sasl.Username != "" {
|
|
options.Set("saslUsername", sasl.Username)
|
|
}
|
|
if sasl.Password != "" {
|
|
options.Set("saslPassword", sasl.Password)
|
|
}
|
|
if sasl.Mechanism != "" {
|
|
options.Set("saslMechanism", sasl.Mechanism)
|
|
}
|
|
if sasl.ScramSHASize != "" {
|
|
options.Set("saslSCRAMSHASize", sasl.ScramSHASize)
|
|
}
|
|
}
|
|
dsn := fmt.Sprintf("kafka://%s?%s", configuration.Spec.Broker.Kafka.Brokers[0], options.Encode())
|
|
|
|
_, err := settings.CreateOrUpdate(ctx, fmt.Sprintf("%s-broker", configuration.Name),
|
|
"broker.dsn", dsn, stackNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if configuration.Spec.Broker.Nats != nil {
|
|
|
|
options := url.Values{}
|
|
if configuration.Spec.Broker.Nats.Replicas != 0 {
|
|
options.Set("replicas", fmt.Sprint(configuration.Spec.Broker.Nats.Replicas))
|
|
}
|
|
dsn := fmt.Sprintf("nats://%s?%s", configuration.Spec.Broker.Nats.URL, options.Encode())
|
|
|
|
_, err := settings.CreateOrUpdate(ctx, fmt.Sprintf("%s-broker", configuration.Name),
|
|
"broker.dsn", dsn, stackNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
options := url.Values{}
|
|
if configuration.Spec.Temporal.TLS.SecretName != "" {
|
|
options.Set("secret", configuration.Spec.Temporal.TLS.SecretName)
|
|
}
|
|
_, err := settings.CreateOrUpdate(ctx, fmt.Sprintf("%s-temporal-dsn", configuration.Name),
|
|
"temporal.dsn",
|
|
fmt.Sprintf("temporal://%s/%s?%s", configuration.Spec.Temporal.Address, configuration.Spec.Temporal.Namespace, options.Encode()),
|
|
stackNames...,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if configuration.Spec.Temporal.TLS.CRT != "" {
|
|
_, err = settings.CreateOrUpdate(ctx, fmt.Sprintf("%s-temporal-tls-crt", configuration.Name),
|
|
"temporal.tls.crt", configuration.Spec.Temporal.TLS.CRT, stackNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = settings.CreateOrUpdate(ctx, fmt.Sprintf("%s-temporal-tls-key", configuration.Name),
|
|
"temporal.tls.key", configuration.Spec.Temporal.TLS.Key, stackNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
basicAuth := ""
|
|
options = url.Values{}
|
|
if basicAuthConf := configuration.Spec.Services.Search.ElasticSearchConfig.BasicAuth; basicAuthConf != nil {
|
|
if basicAuthConf.Username != "" {
|
|
basicAuth = fmt.Sprintf("%s:%s@", basicAuthConf.Username, basicAuthConf.Password)
|
|
}
|
|
if basicAuthConf.SecretName != "" {
|
|
options.Set("secret", basicAuthConf.SecretName)
|
|
}
|
|
}
|
|
if configuration.Spec.Services.Search.ElasticSearchConfig.TLS.Enabled {
|
|
options.Set("tls", "true")
|
|
if configuration.Spec.Services.Search.ElasticSearchConfig.TLS.SkipCertVerify {
|
|
options.Set("skipCertVerify", "true")
|
|
}
|
|
}
|
|
|
|
elasticSearchDSN := fmt.Sprintf(
|
|
"%s://%s%s:%d?%s",
|
|
configuration.Spec.Services.Search.ElasticSearchConfig.Scheme,
|
|
basicAuth,
|
|
configuration.Spec.Services.Search.ElasticSearchConfig.Host,
|
|
configuration.Spec.Services.Search.ElasticSearchConfig.Port,
|
|
options.Encode(),
|
|
)
|
|
|
|
_, err = settings.CreateOrUpdate(ctx, fmt.Sprintf("%s-elasticsearch-dsn", configuration.Name),
|
|
"elasticsearch.dsn", elasticSearchDSN, stackNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
batchingConfig := ""
|
|
|
|
if configuration.Spec.Services.Search.Batching.Count != 0 {
|
|
batchingConfig = fmt.Sprintf("count=%d", configuration.Spec.Services.Search.Batching.Count)
|
|
}
|
|
|
|
if configuration.Spec.Services.Search.Batching.Period != "" {
|
|
if len(batchingConfig) > 0 {
|
|
batchingConfig = batchingConfig + ","
|
|
}
|
|
batchingConfig = batchingConfig + "period=" + configuration.Spec.Services.Search.Batching.Period
|
|
}
|
|
if batchingConfig != "" {
|
|
_, err = settings.CreateOrUpdate(ctx, fmt.Sprintf("%s-search-batching", configuration.Name),
|
|
"search.batching", batchingConfig, stackNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(configuration.Spec.Registries) > 0 {
|
|
for name, config := range configuration.Spec.Registries {
|
|
_, err = settings.CreateOrUpdate(ctx, fmt.Sprintf("%s-registries-%s", configuration.Name, name),
|
|
fmt.Sprintf("registries.%s.endpoint", name), config.Endpoint, stackNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if configuration.Spec.Services.Payments.EncryptionKey != "" {
|
|
_, err = settings.CreateOrUpdate(ctx, fmt.Sprintf("%s-payments-encryption-key", configuration.Name),
|
|
"payments.encryption-key", configuration.Spec.Services.Payments.EncryptionKey, stackNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
type serviceAnnotationsDescriptor struct {
|
|
annotations map[string]string
|
|
name string
|
|
}
|
|
for _, cfg := range []serviceAnnotationsDescriptor{
|
|
{
|
|
annotations: configuration.Spec.Services.Ledger.Annotations.Service,
|
|
name: "ledger",
|
|
},
|
|
{
|
|
annotations: configuration.Spec.Services.Payments.Annotations.Service,
|
|
name: "payments",
|
|
},
|
|
{
|
|
annotations: configuration.Spec.Services.Orchestration.Annotations.Service,
|
|
name: "orchestration",
|
|
},
|
|
{
|
|
annotations: configuration.Spec.Services.Auth.Annotations.Service,
|
|
name: "auth",
|
|
},
|
|
{
|
|
annotations: configuration.Spec.Services.Webhooks.Annotations.Service,
|
|
name: "webhooks",
|
|
},
|
|
{
|
|
annotations: configuration.Spec.Services.Reconciliation.Annotations.Service,
|
|
name: "reconciliation",
|
|
},
|
|
{
|
|
annotations: configuration.Spec.Services.Gateway.Annotations.Service,
|
|
name: "gateway",
|
|
},
|
|
{
|
|
annotations: configuration.Spec.Services.Wallets.Annotations.Service,
|
|
name: "wallets",
|
|
},
|
|
{
|
|
annotations: configuration.Spec.Services.Stargate.Annotations.Service,
|
|
name: "stargate",
|
|
},
|
|
{
|
|
annotations: configuration.Spec.Services.Search.Annotations.Service,
|
|
name: "search",
|
|
},
|
|
} {
|
|
if cfg.annotations == nil || len(cfg.annotations) == 0 {
|
|
continue
|
|
}
|
|
|
|
computed := ""
|
|
for key, value := range cfg.annotations {
|
|
computed = fmt.Sprintf("%s=%s,%s", key, value, computed)
|
|
}
|
|
computed = computed[:len(computed)-1]
|
|
|
|
_, err = settings.CreateOrUpdate(ctx, fmt.Sprintf("%s-%s-service-annotations", configuration.Name, cfg.name),
|
|
fmt.Sprintf("services.%s.annotations", cfg.name),
|
|
computed, stackNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
Init(
|
|
WithReconciler[*v1beta3.Configuration](Reconcile,
|
|
WithWatch[*v1beta3.Configuration, *v1beta3.Stack](func(ctx Context, object *v1beta3.Stack) []reconcile.Request {
|
|
return []reconcile.Request{{
|
|
NamespacedName: types.NamespacedName{
|
|
Name: object.Spec.Seed,
|
|
},
|
|
}}
|
|
}),
|
|
),
|
|
)
|
|
}
|