Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Kibana basepath in associations #8053

Merged
merged 21 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/controller/agent/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ func getFleetSetupFleetEnvVars(client k8s.Client, fleetToken EnrollmentAPIKey, f
client,
types.NamespacedName{Namespace: agent.Namespace, Name: HTTPServiceName(agent.Name)},
agent.Spec.HTTP.Protocol(),
"",
)
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/association/controller/agent_fleetserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func getFleetServerExternalURL(c k8s.Client, assoc commonv1.Association) (string
serviceName = agent.HTTPServiceName(fleetServer.Name)
}
nsn := types.NamespacedName{Namespace: fleetServer.Namespace, Name: serviceName}
return association.ServiceURL(c, nsn, fleetServer.Spec.HTTP.Protocol())
return association.ServiceURL(c, nsn, fleetServer.Spec.HTTP.Protocol(), "")
}

type fleetVersionResponse struct {
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/association/controller/apm_es.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func getElasticsearchExternalURL(c k8s.Client, assoc commonv1.Association) (stri
serviceName = services.ExternalServiceName(es.Name)
}
nsn := types.NamespacedName{Name: serviceName, Namespace: es.Namespace}
return association.ServiceURL(c, nsn, es.Spec.HTTP.Protocol())
return association.ServiceURL(c, nsn, es.Spec.HTTP.Protocol(), "")
}

// getAPMElasticsearchRoles returns for a given version of the APM Server the set of required roles.
Expand Down
10 changes: 9 additions & 1 deletion pkg/controller/association/controller/apm_kibana.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/operator"
ver "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/version"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/user"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana"
kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
"github.com/elastic/cloud-on-k8s/v2/pkg/utils/rbac"
Expand Down Expand Up @@ -69,7 +70,14 @@ func getKibanaExternalURL(c k8s.Client, assoc commonv1.Association) (string, err
serviceName = kbv1.HTTPService(kb.Name)
}
nsn := types.NamespacedName{Namespace: kb.Namespace, Name: serviceName}
return association.ServiceURL(c, nsn, kb.Spec.HTTP.Protocol())

// Get Kibana base path if configured
basePath, err := kibana.GetKibanaBasePath(kb)
if err != nil {
return "", err
}

return association.ServiceURL(c, nsn, kb.Spec.HTTP.Protocol(), basePath)
}

type kibanaVersionResponse struct {
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/association/controller/kibana_ent.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func getEntExternalURL(c k8s.Client, assoc commonv1.Association) (string, error)
serviceName = entctl.HTTPServiceName(ent.Name)
}
nsn := types.NamespacedName{Namespace: ent.Namespace, Name: serviceName}
return association.ServiceURL(c, nsn, ent.Spec.HTTP.Protocol())
return association.ServiceURL(c, nsn, ent.Spec.HTTP.Protocol(), "")
}

type entVersionResponse struct {
Expand Down
6 changes: 3 additions & 3 deletions pkg/controller/association/reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ var (
serviceName = services.ExternalServiceName(es.Name)
}
nsn := types.NamespacedName{Name: serviceName, Namespace: es.Namespace}
return ServiceURL(c, nsn, es.Spec.HTTP.Protocol())
return ServiceURL(c, nsn, es.Spec.HTTP.Protocol(), "")
},
AssociationName: "kb-es",
AssociatedShortName: "kb",
Expand Down Expand Up @@ -540,7 +540,7 @@ func TestReconciler_Reconcile_noESAuth(t *testing.T) {
serviceName = "entname-ent-http"
}
nsn := types.NamespacedName{Namespace: ent.Namespace, Name: serviceName}
return ServiceURL(c, nsn, ent.Spec.HTTP.Protocol())
return ServiceURL(c, nsn, ent.Spec.HTTP.Protocol(), "")
},
ReferencedResourceVersion: func(c k8s.Client, association commonv1.Association) (string, bool, error) {
entRef := association.AssociationRef()
Expand Down Expand Up @@ -1095,7 +1095,7 @@ func TestReconciler_Reconcile_Transitive_Associations(t *testing.T) {
serviceName = agentNamer.Suffix(fleetServer.Name, "http")
}
nsn := types.NamespacedName{Namespace: fleetServer.Namespace, Name: serviceName}
url, err := ServiceURL(c, nsn, fleetServer.Spec.HTTP.Protocol())
url, err := ServiceURL(c, nsn, fleetServer.Spec.HTTP.Protocol(), "")
if err != nil {
return "", err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/controller/association/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

// ServiceURL calculates the URL for the service identified by serviceNSN using protocol.
func ServiceURL(c k8s.Client, serviceNSN types.NamespacedName, protocol string) (string, error) {
func ServiceURL(c k8s.Client, serviceNSN types.NamespacedName, protocol string, basePath string) (string, error) {
var svc corev1.Service
if err := c.Get(context.Background(), serviceNSN, &svc); err != nil {
return "", fmt.Errorf("while fetching referenced service: %w", err)
Expand All @@ -26,7 +26,7 @@ func ServiceURL(c k8s.Client, serviceNSN types.NamespacedName, protocol string)
return "", err
}

return fmt.Sprintf("%s://%s.%s.svc:%d", protocol, svc.Name, svc.Namespace, port), nil
return fmt.Sprintf("%s://%s.%s.svc:%d%s", protocol, svc.Name, svc.Namespace, port, basePath), nil
}

// findPortFor returns the port with the name matching protocol.
Expand Down
14 changes: 13 additions & 1 deletion pkg/controller/association/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func TestServiceURL(t *testing.T) {
c k8s.Client
serviceNSN types.NamespacedName
protocol string
basePath string
}
svcName := types.NamespacedName{Namespace: "a", Name: "b"}
svcFixture := &corev1.Service{
Expand Down Expand Up @@ -65,10 +66,21 @@ func TestServiceURL(t *testing.T) {
want: "",
wantErr: true,
},
{
name: "happy path with base path",
args: args{
c: k8s.NewFakeClient(svcFixture),
serviceNSN: svcName,
protocol: "https",
basePath: "/monitoring/kibana",
},
want: "https://b.a.svc:9200/monitoring/kibana",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ServiceURL(tt.args.c, tt.args.serviceNSN, tt.args.protocol)
got, err := ServiceURL(tt.args.c, tt.args.serviceNSN, tt.args.protocol, tt.args.basePath)
if (err != nil) != tt.wantErr {
t.Errorf("ServiceURL() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down
89 changes: 84 additions & 5 deletions pkg/controller/kibana/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,23 @@ package kibana

import (
"context"
"fmt"
"strconv"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/intstr"
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"

"github.com/elastic/go-ucfg"

kbv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/kibana/v1"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/annotation"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/container"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/defaults"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/keystore"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/pod"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/settings"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/version"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/volume"
kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
Expand All @@ -26,8 +31,10 @@ import (
)

const (
DataVolumeName = "kibana-data"
DataVolumeMountPath = "/usr/share/kibana/data"
DataVolumeName = "kibana-data"
DataVolumeMountPath = "/usr/share/kibana/data"
KibanaBasePathEnvName = "SERVER_BASEPATH"
KibanaRewriteBasePathEnvName = "SERVER_REWRITEBASEPATH"
)

var (
Expand All @@ -52,8 +59,16 @@ var (
}
)

// kibanaConfig is used to get the base path from the Kibana configuration.
type kibanaConfig struct {
Server struct {
RewriteBasePath bool `config:"rewriteBasePath"`
BasePath string `config:"basePath"`
}
}

// readinessProbe is the readiness probe for the Kibana container
func readinessProbe(useTLS bool) corev1.Probe {
func readinessProbe(useTLS bool, basePath string) corev1.Probe {
scheme := corev1.URISchemeHTTP
if useTLS {
scheme = corev1.URISchemeHTTPS
Expand All @@ -67,7 +82,7 @@ func readinessProbe(useTLS bool) corev1.Probe {
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Port: intstr.FromInt(network.HTTPPort),
Path: "/login",
Path: fmt.Sprintf("%s/login", basePath),
Scheme: scheme,
},
},
Expand All @@ -84,12 +99,16 @@ func NewPodTemplateSpec(ctx context.Context, client k8sclient.Client, kb kbv1.Ki
return corev1.PodTemplateSpec{}, err // error unlikely and should have been caught during validation
}

kibanaBasePath, err := GetKibanaBasePath(kb)
if err != nil {
return corev1.PodTemplateSpec{}, fmt.Errorf("failed to get kibana base path error:%w", err)
}
builder := defaults.NewPodTemplateBuilder(kb.Spec.PodTemplate, kbv1.KibanaContainerName).
WithResources(DefaultResources).
WithLabels(labels).
WithAnnotations(DefaultAnnotations).
WithDockerImage(kb.Spec.Image, container.ImageRepository(container.KibanaImage, v)).
WithReadinessProbe(readinessProbe(kb.Spec.HTTP.TLS.Enabled())).
WithReadinessProbe(readinessProbe(kb.Spec.HTTP.TLS.Enabled(), kibanaBasePath)).
WithPorts(ports).
WithInitContainers(initConfigContainer(kb))

Expand All @@ -115,6 +134,66 @@ func GetKibanaContainer(podSpec corev1.PodSpec) *corev1.Container {
return pod.ContainerByName(podSpec, kbv1.KibanaContainerName)
}

func GetKibanaBasePathFromSpecEnv(podSpec corev1.PodSpec) (string, error) {
kbContainer := GetKibanaContainer(podSpec)
if kbContainer == nil {
return "", nil
}

envMap := make(map[string]string)
for _, envVar := range kbContainer.Env {
if envVar.Name == KibanaBasePathEnvName || envVar.Name == KibanaRewriteBasePathEnvName {
envMap[envVar.Name] = envVar.Value
}
}

// If SERVER_REWRITEBASEPATH is set to true, we should use the value of SERVER_BASEPATH
if rewriteBasePath, ok := envMap[KibanaRewriteBasePathEnvName]; ok {
rewriteBasePathBool, err := strconv.ParseBool(rewriteBasePath)
if err != nil {
return "", fmt.Errorf("failed to parse SERVER_REWRITEBASEPATH value %s: %w", rewriteBasePath, err)
}
if rewriteBasePathBool {
return envMap[KibanaBasePathEnvName], nil
}
}

return "", nil
}

func getDefaultContainerPorts(kb kbv1.Kibana) []corev1.ContainerPort {
return []corev1.ContainerPort{{Name: kb.Spec.HTTP.Protocol(), ContainerPort: int32(network.HTTPPort), Protocol: corev1.ProtocolTCP}}
}

func GetKibanaBasePath(kb kbv1.Kibana) (string, error) {
// We only support the case where both base path and rewrite base path are set in the ENV or the config
// We will not support the case where base path is set in the ENV and rewrite base path is set in the config or vice versa
kbBasePath, err := GetKibanaBasePathFromSpecEnv(kb.Spec.PodTemplate.Spec)
if err != nil {
return "", err
}

if kbBasePath != "" {
return kbBasePath, nil
}

if kb.Spec.Config == nil {
return "", nil
}

kbucfgConfig, err := ucfg.NewFrom(kb.Spec.Config.Data, settings.Options...)
if err != nil {
return "", err
}

kbCfg := kibanaConfig{}
if err := kbucfgConfig.Unpack(&kbCfg); err != nil {
return "", err
}

if kbCfg.Server.RewriteBasePath {
return kbCfg.Server.BasePath, nil
}

return "", nil
}
Loading