Skip to content

Commit

Permalink
Add support for UI configuration (#115)
Browse files Browse the repository at this point in the history
* Add support for UI configuration

Signed-off-by: Gary Brown <[email protected]>

* Fix e2e test by merging two UI based tests

Signed-off-by: Gary Brown <[email protected]>

* Address the freeform comment and changing the order of adding the configmap in the controllers

Signed-off-by: Gary Brown <[email protected]>
  • Loading branch information
objectiser authored and jpkrohling committed Nov 21, 2018
1 parent 4d9b695 commit 0439534
Show file tree
Hide file tree
Showing 12 changed files with 369 additions and 10 deletions.
11 changes: 11 additions & 0 deletions deploy/examples/all-in-one-with-options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ spec:
log-level: debug
query:
base-path: /jaeger
ui:
options:
dependencies:
menuEnabled: false
tracking:
gaID: UA-000000-2
menu:
- label: "About Jaeger"
items:
- label: "Documentation"
url: "https://www.jaegertracing.io/docs/latest"
storage:
options:
memory:
Expand Down
40 changes: 40 additions & 0 deletions pkg/apis/io/v1alpha1/freeform.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package v1alpha1

import (
"encoding/json"
)

// FreeForm defines a common options parameter that maintains the hierarchical
// structure of the data, unlike Options which flattens the hierarchy into a
// key/value map where the hierarchy is converted to '.' separated items in the key.
type FreeForm struct {
json []byte
}

// NewFreeForm build a new FreeForm object based on the given map
func NewFreeForm(o map[string]interface{}) FreeForm {
freeForm := FreeForm{}
if o != nil {
freeForm.json, _ = json.Marshal(o)
}
return freeForm
}

// UnmarshalJSON implements an alternative parser for this field
func (o *FreeForm) UnmarshalJSON(b []byte) error {
o.json = b
return nil
}

// MarshalJSON specifies how to convert this object into JSON
func (o FreeForm) MarshalJSON() ([]byte, error) {
if len(o.json) == 0 {
return []byte("{}"), nil
}
return o.json, nil
}

// IsEmpty determines if the freeform options are empty
func (o FreeForm) IsEmpty() bool {
return len(o.json) == 0 || string(o.json) == "{}"
}
53 changes: 53 additions & 0 deletions pkg/apis/io/v1alpha1/freeform_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package v1alpha1

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestFreeForm(t *testing.T) {
uiconfig := `{"es":{"password":"changeme","server-urls":"http://elasticsearch:9200","username":"elastic"}}`
o := NewFreeForm(map[string]interface{}{
"es": map[string]interface{}{
"server-urls": "http://elasticsearch:9200",
"username": "elastic",
"password": "changeme",
},
})
json, err := o.MarshalJSON()
assert.NoError(t, err)
assert.NotNil(t, json)
assert.Equal(t, uiconfig, string(o.json))
}

func TestFreeFormUnmarhalMarshal(t *testing.T) {
uiconfig := `{"es":{"password":"changeme","server-urls":"http://elasticsearch:9200","username":"elastic"}}`
o := NewFreeForm(nil)
o.UnmarshalJSON([]byte(uiconfig))
json, err := o.MarshalJSON()
assert.NoError(t, err)
assert.NotNil(t, json)
assert.Equal(t, uiconfig, string(o.json))
}

func TestFreeFormIsEmptyFalse(t *testing.T) {
o := NewFreeForm(map[string]interface{}{
"es": map[string]interface{}{
"server-urls": "http://elasticsearch:9200",
"username": "elastic",
"password": "changeme",
},
})
assert.False(t, o.IsEmpty())
}

func TestFreeFormIsEmptyTrue(t *testing.T) {
o := NewFreeForm(map[string]interface{}{})
assert.True(t, o.IsEmpty())
}

func TestFreeFormIsEmptyNilTrue(t *testing.T) {
o := NewFreeForm(nil)
assert.True(t, o.IsEmpty())
}
6 changes: 6 additions & 0 deletions pkg/apis/io/v1alpha1/jaeger_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type JaegerSpec struct {
Query JaegerQuerySpec `json:"query"`
Collector JaegerCollectorSpec `json:"collector"`
Agent JaegerAgentSpec `json:"agent"`
UI JaegerUISpec `json:"ui"`
Storage JaegerStorageSpec `json:"storage"`
Ingress JaegerIngressSpec `json:"ingress"`
JaegerCommonSpec
Expand All @@ -77,6 +78,11 @@ type JaegerQuerySpec struct {
JaegerCommonSpec
}

// JaegerUISpec defines the options to be used to configure the UI
type JaegerUISpec struct {
Options FreeForm `json:"options"`
}

// JaegerIngressSpec defines the options to be used when deploying the query ingress
type JaegerIngressSpec struct {
Enabled *bool `json:"enabled"`
Expand Down
39 changes: 39 additions & 0 deletions pkg/apis/io/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

95 changes: 95 additions & 0 deletions pkg/configmap/ui.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package configmap

import (
"fmt"

"github.com/sirupsen/logrus"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/jaegertracing/jaeger-operator/pkg/apis/io/v1alpha1"
)

// UIConfig represents a UI configmap
type UIConfig struct {
jaeger *v1alpha1.Jaeger
}

// NewUIConfig builds a new UIConfig struct based on the given spec
func NewUIConfig(jaeger *v1alpha1.Jaeger) *UIConfig {
return &UIConfig{jaeger: jaeger}
}

// Get returns a configmap specification for the current instance
func (u *UIConfig) Get() *v1.ConfigMap {
// Check for empty map
if u.jaeger.Spec.UI.Options.IsEmpty() {
return nil
}

json, err := u.jaeger.Spec.UI.Options.MarshalJSON()
if err != nil {
return nil
}

logrus.Debug("Assembling the UI configmap")
trueVar := true
data := map[string]string{
"ui": string(json),
}

return &v1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-ui-configuration", u.jaeger.Name),
Namespace: u.jaeger.Namespace,
OwnerReferences: []metav1.OwnerReference{
metav1.OwnerReference{
APIVersion: u.jaeger.APIVersion,
Kind: u.jaeger.Kind,
Name: u.jaeger.Name,
UID: u.jaeger.UID,
Controller: &trueVar,
},
},
},
Data: data,
}
}

// Update will modify the supplied common spec and options to include
// support for the UI configmap if appropriate
func Update(jaeger *v1alpha1.Jaeger, commonSpec *v1alpha1.JaegerCommonSpec, options *[]string) {
// Check for empty map
if jaeger.Spec.UI.Options.IsEmpty() {
return
}

volume := v1.Volume{
Name: fmt.Sprintf("%s-ui-configuration-volume", jaeger.Name),
VolumeSource: v1.VolumeSource{
ConfigMap: &v1.ConfigMapVolumeSource{
LocalObjectReference: v1.LocalObjectReference{
Name: fmt.Sprintf("%s-ui-configuration", jaeger.Name),
},
Items: []v1.KeyToPath{
v1.KeyToPath{
Key: "ui",
Path: "ui.json",
},
},
},
},
}
volumeMount := v1.VolumeMount{
Name: fmt.Sprintf("%s-ui-configuration-volume", jaeger.Name),
MountPath: "/etc/config",
ReadOnly: true,
}
commonSpec.Volumes = append(commonSpec.Volumes, volume)
commonSpec.VolumeMounts = append(commonSpec.VolumeMounts, volumeMount)
*options = append(*options, "--query.ui-config=/etc/config/ui.json")
}
73 changes: 73 additions & 0 deletions pkg/configmap/ui_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package configmap

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/jaegertracing/jaeger-operator/pkg/apis/io/v1alpha1"
)

func TestNoUIConfig(t *testing.T) {
jaeger := v1alpha1.NewJaeger("TestNoUIConfig")

config := NewUIConfig(jaeger)
dep := config.Get()
assert.Nil(t, dep)
}

func TestWithEmptyUIConfig(t *testing.T) {
uiconfig := v1alpha1.NewFreeForm(map[string]interface{}{})
jaeger := v1alpha1.NewJaeger("TestWithEmptyUIConfig")
jaeger.Spec.UI.Options = uiconfig

config := NewUIConfig(jaeger)
dep := config.Get()
assert.Nil(t, dep)
}

func TestWithUIConfig(t *testing.T) {
uiconfig := v1alpha1.NewFreeForm(map[string]interface{}{
"tracking": map[string]interface{}{
"gaID": "UA-000000-2",
},
})
json := `{"tracking":{"gaID":"UA-000000-2"}}`
jaeger := v1alpha1.NewJaeger("TestWithUIConfig")
jaeger.Spec.UI.Options = uiconfig

config := NewUIConfig(jaeger)
dep := config.Get()
assert.Equal(t, json, dep.Data["ui"])
}

func TestUpdateNoUIConfig(t *testing.T) {
jaeger := v1alpha1.NewJaeger("TestUpdateNoUIConfig")

commonSpec := v1alpha1.JaegerCommonSpec{}
options := []string{}

Update(jaeger, &commonSpec, &options)
assert.Len(t, commonSpec.Volumes, 0)
assert.Len(t, commonSpec.VolumeMounts, 0)
assert.Len(t, options, 0)
}

func TestUpdateWithUIConfig(t *testing.T) {
uiconfig := v1alpha1.NewFreeForm(map[string]interface{}{
"tracking": map[string]interface{}{
"gaID": "UA-000000-2",
},
})
jaeger := v1alpha1.NewJaeger("TestUpdateWithUIConfig")
jaeger.Spec.UI.Options = uiconfig

commonSpec := v1alpha1.JaegerCommonSpec{}
options := []string{}

Update(jaeger, &commonSpec, &options)
assert.Len(t, commonSpec.Volumes, 1)
assert.Len(t, commonSpec.VolumeMounts, 1)
assert.Len(t, options, 1)
assert.Equal(t, "--query.ui-config=/etc/config/ui.json", options[0])
}
7 changes: 7 additions & 0 deletions pkg/controller/jaeger/all-in-one.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/jaegertracing/jaeger-operator/pkg/account"
"github.com/jaegertracing/jaeger-operator/pkg/apis/io/v1alpha1"
"github.com/jaegertracing/jaeger-operator/pkg/configmap"
"github.com/jaegertracing/jaeger-operator/pkg/deployment"
"github.com/jaegertracing/jaeger-operator/pkg/ingress"
"github.com/jaegertracing/jaeger-operator/pkg/inject"
Expand Down Expand Up @@ -40,6 +41,12 @@ func (c *allInOneController) Create() []runtime.Object {
os = append(os, acc)
}

// add the config map
cm := configmap.NewUIConfig(c.jaeger).Get()
if nil != cm {
os = append(os, cm)
}

// add the deployments
os = append(os, inject.OAuthProxy(c.jaeger, dep.Get()))

Expand Down
Loading

0 comments on commit 0439534

Please sign in to comment.