mirror of
https://github.com/hanzo-fi/stack.git
synced 2026-10-30 20:34:43 +00:00
722 lines
18 KiB
Go
722 lines
18 KiB
Go
package modules
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"golang.org/x/mod/semver"
|
|
|
|
"github.com/formancehq/stack/libs/go-libs/collectionutils"
|
|
|
|
stackv1beta3 "github.com/formancehq/operator/apis/stack/v1beta3"
|
|
"github.com/formancehq/operator/internal/common"
|
|
"github.com/formancehq/operator/internal/controllerutils"
|
|
"github.com/formancehq/stack/libs/go-libs/logging"
|
|
"github.com/nats-io/nats.go"
|
|
corev1 "k8s.io/api/core/v1"
|
|
networkingv1 "k8s.io/api/networking/v1"
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|
"k8s.io/utils/pointer"
|
|
)
|
|
|
|
type Config struct {
|
|
Data map[string]string
|
|
Mount bool
|
|
}
|
|
|
|
func (c Config) create(ctx context.Context, deployer Deployer, serviceName, configName string) (*ConfigHandle, error) {
|
|
|
|
hash := sha256.New()
|
|
dataKeys := collectionutils.Keys(c.Data)
|
|
sort.Strings(dataKeys)
|
|
for _, k := range dataKeys {
|
|
hash.Write([]byte(k))
|
|
hash.Write([]byte(c.Data[k]))
|
|
}
|
|
|
|
configMap, _, err := deployer.
|
|
ConfigMaps().
|
|
CreateOrUpdate(ctx, serviceName+"-"+configName, func(t *corev1.ConfigMap) {
|
|
t.Data = c.Data
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var mountPath string
|
|
if c.Mount {
|
|
mountPath = fmt.Sprintf("/config/%s", configName)
|
|
}
|
|
h := NewConfigHandle(configMap.Name, mountPath, base64.URLEncoding.EncodeToString(hash.Sum(nil)))
|
|
return &h, nil
|
|
}
|
|
|
|
type Configs map[string]Config
|
|
|
|
func (c Configs) create(ctx context.Context, deployer Deployer, serviceName string) (ConfigHandles, error) {
|
|
configHandles := ConfigHandles{}
|
|
for configName, configDefinition := range c {
|
|
configHandle, err := configDefinition.create(ctx, deployer, serviceName, configName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
configHandles[configName] = *configHandle
|
|
}
|
|
return configHandles, nil
|
|
}
|
|
|
|
type Secret struct {
|
|
Data map[string][]byte
|
|
Mount bool
|
|
}
|
|
|
|
func (s Secret) create(ctx context.Context, deployer *scopedResourceDeployer, serviceName, secretName string) (*SecretHandle, error) {
|
|
|
|
hash := sha256.New()
|
|
dataKeys := collectionutils.Keys(s.Data)
|
|
sort.Strings(dataKeys)
|
|
for _, k := range dataKeys {
|
|
hash.Write([]byte(k))
|
|
hash.Write(s.Data[k])
|
|
}
|
|
secret, _, err := deployer.
|
|
Secrets().
|
|
CreateOrUpdate(ctx, serviceName+"-"+secretName, func(t *corev1.Secret) {
|
|
// Only create secret if it does not exist
|
|
if t.Data != nil {
|
|
for k := range s.Data {
|
|
if _, ok := t.Data[k]; !ok {
|
|
goto apply
|
|
}
|
|
}
|
|
return
|
|
}
|
|
apply:
|
|
t.Data = s.Data
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var mountPath string
|
|
if s.Mount {
|
|
mountPath = fmt.Sprintf("/secret/%s", secretName)
|
|
}
|
|
h := NewSecretHandle(secret.Name, mountPath, base64.URLEncoding.EncodeToString(hash.Sum(nil)))
|
|
return &h, nil
|
|
}
|
|
|
|
type Secrets map[string]Secret
|
|
|
|
func (s Secrets) create(ctx context.Context, deployer *scopedResourceDeployer, serviceName string) (SecretHandles, error) {
|
|
secretHandles := SecretHandles{}
|
|
for secretName, secretDefinition := range s {
|
|
secretHandle, err := secretDefinition.create(ctx, deployer, serviceName, secretName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
secretHandles[secretName] = *secretHandle
|
|
}
|
|
return secretHandles, nil
|
|
}
|
|
|
|
type EnvVar struct {
|
|
Name string
|
|
FromString *string
|
|
FromConfig *string
|
|
FromSecret *string
|
|
Key string
|
|
}
|
|
|
|
func Env(name string, value string) EnvVar {
|
|
return EnvVar{
|
|
Name: name,
|
|
FromString: &value,
|
|
}
|
|
}
|
|
|
|
func EnvFromBool(name string, vb bool) EnvVar {
|
|
value := "true"
|
|
if !vb {
|
|
value = "false"
|
|
}
|
|
return EnvVar{
|
|
Name: name,
|
|
FromString: &value,
|
|
}
|
|
}
|
|
|
|
func EnvFromConfig(name, configName, key string) EnvVar {
|
|
return EnvVar{
|
|
Name: name,
|
|
FromConfig: &configName,
|
|
Key: key,
|
|
}
|
|
}
|
|
|
|
func EnvFromSecret(name, secretName, key string) EnvVar {
|
|
return EnvVar{
|
|
Name: name,
|
|
FromSecret: &secretName,
|
|
Key: key,
|
|
}
|
|
}
|
|
|
|
type Liveness int
|
|
|
|
const (
|
|
LivenessDefault = iota
|
|
LivenessLegacy
|
|
LivenessDisable
|
|
)
|
|
|
|
type ContainerEnv []EnvVar
|
|
|
|
func (env ContainerEnv) Append(v ...EnvVar) ContainerEnv {
|
|
return append(env, v...)
|
|
}
|
|
|
|
func NewEnv() ContainerEnv {
|
|
return ContainerEnv{}
|
|
}
|
|
|
|
func (e ContainerEnv) ToCoreEnv() []corev1.EnvVar {
|
|
ret := make([]corev1.EnvVar, 0)
|
|
for _, envVar := range e {
|
|
switch {
|
|
case envVar.FromSecret != nil:
|
|
ret = append(ret, corev1.EnvVar{
|
|
Name: envVar.Name,
|
|
ValueFrom: &corev1.EnvVarSource{
|
|
SecretKeyRef: &corev1.SecretKeySelector{
|
|
Key: envVar.Key,
|
|
LocalObjectReference: corev1.LocalObjectReference{
|
|
Name: *envVar.FromSecret,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
case envVar.FromConfig != nil:
|
|
ret = append(ret, corev1.EnvVar{
|
|
Name: envVar.Name,
|
|
ValueFrom: &corev1.EnvVarSource{
|
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
|
Key: envVar.Key,
|
|
LocalObjectReference: corev1.LocalObjectReference{
|
|
Name: *envVar.FromSecret,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
case envVar.FromString != nil:
|
|
ret = append(ret, corev1.EnvVar{
|
|
Name: envVar.Name,
|
|
Value: *envVar.FromString,
|
|
})
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
type SecretHandle struct {
|
|
MountPath string
|
|
Name string
|
|
Hash string
|
|
}
|
|
|
|
func NewSecretHandle(name, mountPath, hash string) SecretHandle {
|
|
return SecretHandle{
|
|
MountPath: mountPath,
|
|
Name: name,
|
|
Hash: hash,
|
|
}
|
|
}
|
|
|
|
func (h SecretHandle) GetName() string {
|
|
return h.Name
|
|
}
|
|
|
|
func (h SecretHandle) GetMountPath() string {
|
|
return h.MountPath
|
|
}
|
|
|
|
type ConfigHandle struct {
|
|
MountPath string
|
|
Name string
|
|
Hash string
|
|
}
|
|
|
|
func NewConfigHandle(name, mountPath, hash string) ConfigHandle {
|
|
return ConfigHandle{
|
|
MountPath: mountPath,
|
|
Name: name,
|
|
Hash: hash,
|
|
}
|
|
}
|
|
|
|
func (h ConfigHandle) GetName() string {
|
|
return h.Name
|
|
}
|
|
|
|
func (h ConfigHandle) GetMountPath() string {
|
|
if h.MountPath == "" {
|
|
panic("config not defined as mountable")
|
|
}
|
|
return h.MountPath
|
|
}
|
|
|
|
type ConfigHandles map[string]ConfigHandle
|
|
|
|
func (h ConfigHandles) sort() []string {
|
|
ret := make([]string, 0)
|
|
for key := range h {
|
|
ret = append(ret, key)
|
|
}
|
|
sort.Strings(ret)
|
|
return ret
|
|
}
|
|
|
|
type SecretHandles map[string]SecretHandle
|
|
|
|
func (h SecretHandles) sort() []string {
|
|
ret := make([]string, 0)
|
|
for key := range h {
|
|
ret = append(ret, key)
|
|
}
|
|
sort.Strings(ret)
|
|
return ret
|
|
}
|
|
|
|
type ExposeHTTP struct {
|
|
// Path indicates the path used to expose the service using an ingress
|
|
Path string
|
|
Methods []string
|
|
Name string
|
|
}
|
|
|
|
type Topics struct {
|
|
Name string
|
|
}
|
|
|
|
type Path struct {
|
|
Path string
|
|
Methods []string
|
|
Name string
|
|
Secured bool
|
|
}
|
|
|
|
var DefaultExposeHTTP = &ExposeHTTP{}
|
|
|
|
type Service struct {
|
|
Name string
|
|
// Secured indicate if the service is able to handle security
|
|
Secured bool
|
|
// ExposeHTTP indicate the service expose a http endpoint
|
|
ExposeHTTP *ExposeHTTP
|
|
// Paths indicates the paths exposed by the service
|
|
Paths []Path
|
|
// ListenEnvVar indicate the flag used to configure the http service address
|
|
// TODO(gfyrag): Remove this in a future version when all services implements --listen
|
|
ListenEnvVar string
|
|
// Port indicate the listening port of the service.
|
|
// Deprecated
|
|
// All services should have the --listen flag to allow the operator to specify the port
|
|
Port int32
|
|
// Annotations indicates the annotations apply to the Service
|
|
Annotations map[string]string
|
|
InjectPostgresVariables bool
|
|
HasVersionEndpoint bool
|
|
Liveness Liveness
|
|
LivenessEndpoint string
|
|
AuthConfiguration func(config ReconciliationConfig) stackv1beta3.ClientConfiguration
|
|
Configs func(resolveContext ServiceInstallConfiguration) Configs
|
|
Secrets func(resolveContext ServiceInstallConfiguration) Secrets
|
|
Container func(resolveContext ContainerResolutionConfiguration) Container
|
|
InitContainer func(resolveContext ContainerResolutionConfiguration) []Container
|
|
Topics *Topics
|
|
Replicas *int32
|
|
|
|
EnvPrefix string
|
|
}
|
|
|
|
type Services []*Service
|
|
|
|
func (services Services) Len() int {
|
|
return len(services)
|
|
}
|
|
|
|
func (services Services) Less(i, j int) bool {
|
|
return strings.Compare(services[i].Name, services[j].Name) < 0
|
|
}
|
|
|
|
func (services Services) Swap(i, j int) {
|
|
services[i], services[j] = services[j], services[i]
|
|
}
|
|
|
|
type serviceReconciler struct {
|
|
*moduleReconciler
|
|
name string
|
|
service Service
|
|
usedPort int32
|
|
}
|
|
|
|
func (r *serviceReconciler) prepare() {
|
|
if r.service.AuthConfiguration != nil {
|
|
_ = r.Stack.GetOrCreateClient(r.name, r.service.AuthConfiguration(r.ReconciliationConfig))
|
|
}
|
|
if r.service.ExposeHTTP != nil || r.service.Liveness != LivenessDisable {
|
|
r.allocatePort()
|
|
}
|
|
}
|
|
|
|
func (r *serviceReconciler) reconcile(ctx context.Context, config ServiceInstallConfiguration) error {
|
|
|
|
// TODO: Use a job
|
|
if r.Configuration.Spec.Broker.Nats != nil && r.service.Topics != nil {
|
|
r.configureNats()
|
|
}
|
|
|
|
configHandles, err := r.installConfigs(ctx, config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
secretHandles, err := r.installSecrets(ctx, config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if r.service.ExposeHTTP != nil {
|
|
if err := r.install(ctx); err != nil {
|
|
return err
|
|
}
|
|
if r.service.ExposeHTTP.Path != "" {
|
|
if err := r.createIngress(ctx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
err = r.createDeployment(ctx, ContainerResolutionConfiguration{
|
|
ServiceInstallConfiguration: config,
|
|
Configs: configHandles,
|
|
Secrets: secretHandles,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TODO: Properly handle errors
|
|
func (r *serviceReconciler) configureNats() {
|
|
topicName := r.Stack.GetServiceNamespacedName(r.name).Name
|
|
streamConfig := nats.StreamConfig{
|
|
Name: topicName,
|
|
Subjects: []string{topicName},
|
|
Retention: nats.InterestPolicy,
|
|
Replicas: r.Configuration.Spec.Broker.Nats.Replicas,
|
|
}
|
|
nc, err := nats.Connect(r.Configuration.Spec.Broker.Nats.URL)
|
|
if err != nil {
|
|
logging.Error(err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
js, err := nc.JetStream()
|
|
if err != nil {
|
|
logging.Error(err)
|
|
}
|
|
_, err = js.StreamInfo(topicName)
|
|
if err != nil {
|
|
_, err := js.AddStream(&streamConfig)
|
|
if err != nil {
|
|
logging.Error(err)
|
|
}
|
|
} else {
|
|
_, err = js.UpdateStream(&streamConfig)
|
|
if err != nil {
|
|
logging.Error(fmt.Sprintf("%s: %s", topicName, err))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *serviceReconciler) allocatePort() {
|
|
r.usedPort = r.service.Port
|
|
if r.usedPort == 0 {
|
|
r.usedPort = r.portAllocator.NextPort()
|
|
}
|
|
|
|
if r.Stack.Status.Ports == nil {
|
|
r.Stack.Status.Ports = make(map[string]map[string]int32)
|
|
}
|
|
|
|
if r.Stack.Status.Ports[r.module.Name()] == nil {
|
|
r.Stack.Status.Ports[r.module.Name()] = make(map[string]int32)
|
|
}
|
|
|
|
r.Stack.Status.Ports[r.module.Name()][r.name] = r.usedPort
|
|
}
|
|
|
|
func (r *serviceReconciler) installConfigs(ctx context.Context, config ServiceInstallConfiguration) (ConfigHandles, error) {
|
|
if r.service.Configs == nil {
|
|
return ConfigHandles{}, nil
|
|
}
|
|
return r.service.Configs(config).create(ctx, r.namespacedResourceDeployer, r.name)
|
|
}
|
|
|
|
func (r *serviceReconciler) installSecrets(ctx context.Context, config ServiceInstallConfiguration) (SecretHandles, error) {
|
|
if r.service.Secrets == nil {
|
|
return SecretHandles{}, nil
|
|
}
|
|
return r.service.Secrets(config).create(ctx, r.namespacedResourceDeployer, r.name)
|
|
}
|
|
|
|
func (r *serviceReconciler) createDeployment(ctx context.Context, config ContainerResolutionConfiguration) error {
|
|
container := r.service.Container(config)
|
|
volumes, volumesHash := config.volumes(r.name)
|
|
return r.podDeployer.deploy(ctx, pod{
|
|
name: r.name,
|
|
moduleName: r.module.Name(),
|
|
volumes: volumes,
|
|
initContainers: r.initContainers(config, r.name),
|
|
containers: r.containers(config, container, r.name),
|
|
disableRollingUpdate: container.DisableRollingUpdate,
|
|
replicas: r.service.Replicas,
|
|
annotations: map[string]string{
|
|
"stack.formance.cloud/volumes-hash": volumesHash,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (r *serviceReconciler) initContainers(ctx ContainerResolutionConfiguration, serviceName string) []corev1.Container {
|
|
ret := make([]corev1.Container, 0)
|
|
if r.service.InitContainer != nil {
|
|
for _, c := range r.service.InitContainer(ctx) {
|
|
ret = append(ret, r.createContainer(ctx, c, "init-"+serviceName, true))
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (r *serviceReconciler) containers(ctx ContainerResolutionConfiguration, container Container, serviceName string) []corev1.Container {
|
|
return []corev1.Container{
|
|
r.createContainer(ctx, container, serviceName, false),
|
|
}
|
|
}
|
|
|
|
func (r *serviceReconciler) createContainer(ctx ContainerResolutionConfiguration, container Container, serviceName string, init bool) corev1.Container {
|
|
c := corev1.Container{
|
|
Name: func() string {
|
|
if container.Name != "" {
|
|
return container.Name
|
|
}
|
|
return serviceName
|
|
}(),
|
|
Image: ctx.Configuration.ResolveImage(container.Image),
|
|
ImagePullPolicy: GetPullPolicy(container.Image),
|
|
Command: container.Command,
|
|
Args: container.Args,
|
|
Resources: container.Resources,
|
|
}
|
|
env := NewEnv()
|
|
if r.service.InjectPostgresVariables {
|
|
env = env.Append(
|
|
DefaultPostgresEnvVarsWithPrefix(*ctx.PostgresConfig, r.Stack.GetServiceName(r.module.Name()), r.service.EnvPrefix)...,
|
|
)
|
|
}
|
|
if r.service.ListenEnvVar != "" {
|
|
env = env.Append(
|
|
Env(fmt.Sprintf("%s%s", r.service.EnvPrefix, r.service.ListenEnvVar), fmt.Sprintf(":%d", r.usedPort)),
|
|
)
|
|
}
|
|
|
|
if r.Configuration.Spec.Monitoring != nil {
|
|
if r.Configuration.Spec.Monitoring.Traces != nil {
|
|
env = env.Append(
|
|
MonitoringTracesEnvVars(r.Configuration.Spec.Monitoring.Traces, r.service.EnvPrefix)...,
|
|
)
|
|
}
|
|
if r.Configuration.Spec.Monitoring.Metrics != nil {
|
|
env = env.Append(
|
|
MonitoringMetricsEnvVars(r.Configuration.Spec.Monitoring.Metrics, r.service.EnvPrefix)...,
|
|
)
|
|
}
|
|
}
|
|
|
|
if !init {
|
|
env = env.Append(
|
|
Env(fmt.Sprintf("%sDEBUG", r.service.EnvPrefix), fmt.Sprintf("%v", r.Stack.Spec.Debug || container.Debug)),
|
|
Env(fmt.Sprintf("%sDEV", r.service.EnvPrefix), fmt.Sprintf("%v", r.Stack.Spec.Dev)),
|
|
// TODO: the stack url is a full url, we can target the gateway. Need to find how to generalize this
|
|
// as the gateway is a component like another
|
|
Env(fmt.Sprintf("%sSTACK_PUBLIC_URL", r.service.EnvPrefix), r.Stack.PublicURL()),
|
|
Env(fmt.Sprintf("%sOTEL_SERVICE_NAME", r.service.EnvPrefix), serviceName),
|
|
Env("STACK", r.Stack.Name),
|
|
)
|
|
}
|
|
|
|
if !semver.IsValid(r.Versions.Spec.Gateway) {
|
|
env = env.Append(
|
|
Env(fmt.Sprintf("%sSTACK_URL", r.service.EnvPrefix), r.Stack.URL()),
|
|
Env(fmt.Sprintf("%sAUTH_ISSUER", r.service.EnvPrefix), fmt.Sprintf("%s/api/auth", r.Stack.URL())),
|
|
)
|
|
} else {
|
|
switch semver.Compare(r.Versions.Spec.Gateway, "v2.0.0-alpha") {
|
|
case -1:
|
|
env = env.Append(
|
|
Env(fmt.Sprintf("%sSTACK_URL", r.service.EnvPrefix), r.Stack.PublicURL()),
|
|
Env(fmt.Sprintf("%sAUTH_ISSUER", r.service.EnvPrefix), fmt.Sprintf("%s/api/auth", r.Stack.PublicURL())),
|
|
)
|
|
default:
|
|
env = env.Append(
|
|
Env(fmt.Sprintf("%sSTACK_URL", r.service.EnvPrefix), r.Stack.URL()),
|
|
Env(fmt.Sprintf("%sAUTH_ISSUER", r.service.EnvPrefix), fmt.Sprintf("%s/api/auth", r.Stack.URL())),
|
|
)
|
|
}
|
|
}
|
|
|
|
for _, envVar := range container.Env {
|
|
envVar.Name = fmt.Sprintf("%s%s", r.service.EnvPrefix, envVar.Name)
|
|
env = append(env, envVar)
|
|
}
|
|
|
|
c.Env = env.ToCoreEnv()
|
|
|
|
if !init {
|
|
ret := make([]corev1.VolumeMount, 0)
|
|
for _, configName := range ctx.Configs.sort() {
|
|
config := ctx.Configs[configName]
|
|
if config.MountPath == "" {
|
|
continue
|
|
}
|
|
ret = append(ret, corev1.VolumeMount{
|
|
Name: configName,
|
|
ReadOnly: true,
|
|
MountPath: fmt.Sprintf("/config/%s", configName),
|
|
})
|
|
}
|
|
for _, secretName := range ctx.Secrets.sort() {
|
|
secret := ctx.Secrets[secretName]
|
|
if secret.MountPath == "" {
|
|
continue
|
|
}
|
|
ret = append(ret, corev1.VolumeMount{
|
|
Name: secretName,
|
|
ReadOnly: true,
|
|
MountPath: fmt.Sprintf("/secret/%s", secretName),
|
|
})
|
|
}
|
|
c.VolumeMounts = ret
|
|
|
|
switch r.service.Liveness {
|
|
case LivenessDefault:
|
|
c.LivenessProbe = common.DefaultLiveness(r.GetUsedPort())
|
|
case LivenessLegacy:
|
|
c.LivenessProbe = common.LegacyLiveness(r.GetUsedPort())
|
|
}
|
|
|
|
if r.service.LivenessEndpoint != "" {
|
|
c.LivenessProbe = common.LivenessEndpoint(r.service.LivenessEndpoint)
|
|
}
|
|
|
|
if r.usedPort != 0 {
|
|
c.Ports = common.SinglePort("http", r.usedPort)
|
|
}
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (r *serviceReconciler) install(ctx context.Context) error {
|
|
return controllerutils.JustError(r.namespacedResourceDeployer.Services().CreateOrUpdate(ctx, r.name, func(t *corev1.Service) {
|
|
annotations := r.service.Annotations
|
|
if annotations == nil {
|
|
annotations = map[string]string{}
|
|
} else {
|
|
annotations = collectionutils.CopyMap(annotations)
|
|
}
|
|
t.ObjectMeta.Annotations = annotations
|
|
|
|
selector := r.name
|
|
if r.Configuration.Spec.LightMode {
|
|
selector = r.Stack.Name
|
|
}
|
|
t.Labels = map[string]string{
|
|
"app.kubernetes.io/service-name": r.name,
|
|
}
|
|
t.Spec = corev1.ServiceSpec{
|
|
Ports: []corev1.ServicePort{{
|
|
Name: "http",
|
|
Port: r.usedPort,
|
|
Protocol: "TCP",
|
|
AppProtocol: pointer.String("http"),
|
|
TargetPort: intstr.FromInt(int(r.usedPort)),
|
|
}},
|
|
Selector: map[string]string{
|
|
"app.kubernetes.io/name": selector,
|
|
},
|
|
}
|
|
}))
|
|
}
|
|
|
|
func (r *serviceReconciler) createIngress(ctx context.Context) error {
|
|
return controllerutils.JustError(r.namespacedResourceDeployer.Ingresses().CreateOrUpdate(ctx, r.name, func(ingress *networkingv1.Ingress) {
|
|
annotations := r.Configuration.Spec.Ingress.Annotations
|
|
if annotations == nil {
|
|
annotations = map[string]string{}
|
|
} else {
|
|
annotations = collectionutils.CopyMap(annotations)
|
|
}
|
|
|
|
pathType := networkingv1.PathTypePrefix
|
|
ingress.ObjectMeta.Annotations = annotations
|
|
ingress.Spec.TLS = func() []networkingv1.IngressTLS {
|
|
if r.Configuration.Spec.Ingress.TLS == nil {
|
|
return nil
|
|
}
|
|
return []networkingv1.IngressTLS{{
|
|
SecretName: r.Configuration.Spec.Ingress.TLS.SecretName,
|
|
Hosts: []string{r.Stack.Spec.Host},
|
|
}}
|
|
}()
|
|
ingress.Spec.Rules = []networkingv1.IngressRule{
|
|
{
|
|
Host: r.Stack.Spec.Host,
|
|
IngressRuleValue: networkingv1.IngressRuleValue{
|
|
HTTP: &networkingv1.HTTPIngressRuleValue{
|
|
Paths: []networkingv1.HTTPIngressPath{
|
|
{
|
|
Path: r.service.ExposeHTTP.Path,
|
|
PathType: &pathType,
|
|
Backend: networkingv1.IngressBackend{
|
|
Service: &networkingv1.IngressServiceBackend{
|
|
Name: r.name,
|
|
Port: networkingv1.ServiceBackendPort{
|
|
Name: "http",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}))
|
|
}
|
|
|
|
func (r *serviceReconciler) GetUsedPort() int32 {
|
|
return r.usedPort
|
|
}
|
|
|
|
func newServiceReconciler(moduleReconciler *moduleReconciler, service Service, name string) *serviceReconciler {
|
|
return &serviceReconciler{
|
|
moduleReconciler: moduleReconciler,
|
|
name: name,
|
|
service: service,
|
|
}
|
|
}
|