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

Add support repetitive arguments to operand #1434

Merged
merged 6 commits into from
Aug 5, 2021
Merged
Show file tree
Hide file tree
Changes from 5 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
64 changes: 52 additions & 12 deletions pkg/apis/jaegertracing/v1/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,28 @@ import (
"strings"
)

// Values hold a map, with string as the key and either a string or a slice of strings as the value
type Values map[string]interface{}

// DeepCopy indicate how to do a deep copy of Values type
func (v *Values) DeepCopy() *Values {
out := make(Values, len(*v))
for key, val := range *v {
switch val.(type) {
case string:
out[key] = val

case []string:
out[key] = append([]string(nil), val.([]string)...)
}
}
return &out
}

// Options defines a common options parameter to the different structs
type Options struct {
opts map[string]string `json:"-"`
json *[]byte `json:"-"`
opts Values `json:"-"`
json *[]byte `json:"-"`
}

// NewOptions build a new Options object based on the given map
Expand All @@ -23,7 +41,7 @@ func NewOptions(o map[string]interface{}) Options {
// Filter creates a new Options object with just the elements identified by the supplied prefix
func (o *Options) Filter(prefix string) Options {
options := Options{}
options.opts = make(map[string]string)
options.opts = make(map[string]interface{})

archivePrefix := prefix + "-archive."
prefix += "."
Expand All @@ -45,7 +63,6 @@ func (o *Options) UnmarshalJSON(b []byte) error {
if err := d.Decode(&entries); err != nil {
return err
}

o.parse(entries)
o.json = &b
return nil
Expand All @@ -69,18 +86,24 @@ func (o Options) MarshalJSON() ([]byte, error) {
}

func (o *Options) parse(entries map[string]interface{}) {
o.opts = make(map[string]string)
o.opts = make(map[string]interface{})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have a structure with this signature already?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find it, We have FreeForm but that is a different structure with json *[]byte

for k, v := range entries {
o.opts = entry(o.opts, k, v)
}
}

func entry(entries map[string]string, key string, value interface{}) map[string]string {
func entry(entries map[string]interface{}, key string, value interface{}) map[string]interface{} {
switch value.(type) {
case map[string]interface{}:
for k, v := range value.(map[string]interface{}) {
entries = entry(entries, fmt.Sprintf("%s.%v", key, k), v)
}
case []interface{}:
values := make([]string, 0, len(value.([]interface{})))
for _, v := range value.([]interface{}) {
values = append(values, v.(string))
}
entries[key] = values
case interface{}:
entries[key] = fmt.Sprintf("%v", value)
}
Expand All @@ -91,24 +114,41 @@ func entry(entries map[string]string, key string, value interface{}) map[string]
// ToArgs converts the options to a value suitable for the Container.Args field
func (o *Options) ToArgs() []string {
if len(o.opts) > 0 {
i := 0
args := make([]string, len(o.opts))
args := make([]string, 0, len(o.opts))
for k, v := range o.opts {
args[i] = fmt.Sprintf("--%s=%v", k, v)
i++
switch v.(type) {
case string:
args = append(args, fmt.Sprintf("--%s=%v", k, v))
case []string:
for _, vv := range v.([]string) {
args = append(args, fmt.Sprintf("--%s=%v", k, vv))
}
}
}
return args
}

return nil
}

// Map returns a map representing the option entries. Items are flattened, with dots as separators. For instance
// an option "cassandra" with a nested "servers" object becomes an entry with the key "cassandra.servers"
func (o *Options) Map() map[string]string {
func (o *Options) Map() map[string]interface{} {
return o.opts
}

// StringMap returns a map representing the option entries,excluding entries that have multiple values.
// Items are flattened, with dots as separators in the same way as Map does.
func (o *Options) StringMap() map[string]string {
smap := make(map[string]string)
for k, v := range o.opts {
switch v.(type) {
case string:
smap[k] = v.(string)
}
}
return smap
}

// GenericMap returns the map representing the option entries as interface{}, suitable for usage with NewOptions()
func (o *Options) GenericMap() map[string]interface{} {
out := make(map[string]interface{})
Expand Down
36 changes: 36 additions & 0 deletions pkg/apis/jaegertracing/v1/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

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

func TestSimpleOption(t *testing.T) {
Expand Down Expand Up @@ -148,3 +149,38 @@ func TestUpdate(t *testing.T) {
// verify
assert.Equal(t, o.opts["key"], "new")
}

func TestStringMap(t *testing.T) {
o := NewOptions(nil)
err := o.UnmarshalJSON([]byte(`{"firstsarg":"v1", "additional-headers":["whatever:thing", "access-control-allow-origin:blerg"]}`))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add an example with real values here? I'm failing to see why additional-headers should be removed instead of having something like --firstsarg=v1 --additional-headers="whatever:thing" --additional-headers="access-control-allow-origin:blerg"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add some real examples,

This is testing a new method I introduced in this PR. StringMap, this method only returns a map with strings (mimic the old behavior when we didn't support arrays) , it omits repeated arguments, I did in this way because this map is used in a lot of places, and I wand to preserve that behaviour for those places which is used, also I don't want to do type asserting each time the option map is used.

require.NoError(t, err)
expected := map[string]string{"firstsarg": "v1"}
strMap := o.StringMap()
assert.Len(t, strMap, 1)
assert.Equal(t, expected, strMap)
}

func TestDeepCopy(t *testing.T) {
o1 := NewOptions(nil)
err := o1.UnmarshalJSON([]byte(`{"firstsarg":"v1", "additional-headers":["whatever:thing", "access-control-allow-origin:blerg"]}`))
require.NoError(t, err)
copy := o1.opts.DeepCopy()

assert.Equal(t, copy, &(o1.opts))
}

func TestRepetitiveArguments(t *testing.T) {
o := NewOptions(nil)
err := o.UnmarshalJSON([]byte(`{"firstsarg":"v1", "additional-headers":["whatever:thing", "access-control-allow-origin:blerg"]}`))
require.NoError(t, err)
expected := []string{"--additional-headers=access-control-allow-origin:blerg", "--additional-headers=whatever:thing", "--firstsarg=v1"}

args := o.ToArgs()
sort.SliceStable(args, func(i, j int) bool {
return args[i] < args[j]
})

assert.Len(t, args, 3)
assert.Equal(t, expected, args)

}
18 changes: 11 additions & 7 deletions pkg/apis/jaegertracing/v1/zz_generated.deepcopy.go

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

2 changes: 1 addition & 1 deletion pkg/cronjob/es_index_cleaner.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func CreateEsIndexCleaner(jaeger *v1.Jaeger) *batchv1beta1.CronJob {

envFromSource := util.CreateEnvsFromSecret(jaeger.Spec.Storage.SecretName)
envs := EsScriptEnvVars(jaeger.Spec.Storage.Options)
if val, ok := jaeger.Spec.Storage.Options.Map()["es.use-aliases"]; ok && strings.EqualFold(val, "true") {
if val, ok := jaeger.Spec.Storage.Options.StringMap()["es.use-aliases"]; ok && strings.EqualFold(val, "true") {
envs = append(envs, corev1.EnvVar{Name: "ROLLOVER", Value: "true"})
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/cronjob/es_rollover.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func EsScriptEnvVars(opts v1.Options) []corev1.EnvVar {
{flag: "es.tls.key", envVar: "ES_TLS_KEY"},
{flag: "es.tls.skip-host-verify", envVar: "ES_TLS_SKIP_HOST_VERIFY"},
}
options := opts.Map()
options := opts.StringMap()
var envs []corev1.EnvVar
for _, x := range scriptEnvVars {
if val, ok := options[x.flag]; ok {
Expand Down
4 changes: 2 additions & 2 deletions pkg/cronjob/spark_dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func CreateSparkDependencies(jaeger *v1.Jaeger) *batchv1beta1.CronJob {
}

func getStorageEnvs(s v1.JaegerStorageSpec) []corev1.EnvVar {
sFlagsMap := s.Options.Map()
sFlagsMap := s.Options.StringMap()
switch s.Type {
case v1.JaegerCassandraStorage:
keyspace := sFlagsMap["cassandra.keyspace"]
Expand Down Expand Up @@ -156,7 +156,7 @@ func getStorageEnvs(s v1.JaegerStorageSpec) []corev1.EnvVar {
}

func logTLSNotSupported(j *v1.Jaeger) {
sFlagsMap := j.Spec.Storage.Options.Map()
sFlagsMap := j.Spec.Storage.Options.StringMap()
if strings.EqualFold(sFlagsMap["es.tls.enabled"], "true") || strings.EqualFold(sFlagsMap["es.tls"], "true") {
j.Logger().Warn("Spark dependencies does not support TLS with Elasticsearch, consider disabling dependencies")
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/ingress/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ func (i *QueryIngress) Get() *networkingv1.Ingress {
func (i *QueryIngress) addRulesSpec(spec *networkingv1.IngressSpec, backend *networkingv1.IngressBackend) {
path := ""

if allInOneQueryBasePath, ok := i.jaeger.Spec.AllInOne.Options.Map()["query.base-path"]; ok && i.jaeger.Spec.Strategy == v1.DeploymentStrategyAllInOne {
if allInOneQueryBasePath, ok := i.jaeger.Spec.AllInOne.Options.StringMap()["query.base-path"]; ok && i.jaeger.Spec.Strategy == v1.DeploymentStrategyAllInOne {
path = allInOneQueryBasePath
} else if queryBasePath, ok := i.jaeger.Spec.Query.Options.Map()["query.base-path"]; ok && i.jaeger.Spec.Strategy == v1.DeploymentStrategyProduction {
} else if queryBasePath, ok := i.jaeger.Spec.Query.Options.StringMap()["query.base-path"]; ok && i.jaeger.Spec.Strategy == v1.DeploymentStrategyProduction {
path = queryBasePath
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/service/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func GetPortNameForGRPC(jaeger *v1.Jaeger) string {

// perhaps the user has provisioned the certs and configured the CR manually?
// for that, we check whether the CLI option `collector.grpc.tls.enabled` was set for the collector
if val, ok := jaeger.Spec.Collector.Options.Map()["collector.grpc.tls.enabled"]; ok {
if val, ok := jaeger.Spec.Collector.Options.StringMap()["collector.grpc.tls.enabled"]; ok {
enabled, err := strconv.ParseBool(val)
if err != nil {
return "grpc-http" // not "true", defaults to false
Expand Down
12 changes: 7 additions & 5 deletions pkg/storage/cassandra_dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func cassandraDeps(jaeger *v1.Jaeger) []batchv1.Job {
Value: jaeger.Spec.Storage.CassandraCreateSchema.Datacenter,
}}

host := jaeger.Spec.Storage.Options.Map()["cassandra.servers"]
host := jaeger.Spec.Storage.Options.StringMap()["cassandra.servers"]
if host == "" {
jaeger.Logger().Info("Cassandra hostname not specified. Using 'cassandra' for the cassandra-create-schema job.")
host = "cassandra" // this is the default in the image
Expand All @@ -54,7 +54,7 @@ func cassandraDeps(jaeger *v1.Jaeger) []batchv1.Job {
Value: host,
})

port := jaeger.Spec.Storage.Options.Map()["cassandra.port"]
port := jaeger.Spec.Storage.Options.StringMap()["cassandra.port"]
if port == "" {
jaeger.Logger().Info("Cassandra port not specified. Using '9042' for the cassandra-create-schema job.")
port = "9042" // this is the default in the image
Expand All @@ -64,7 +64,7 @@ func cassandraDeps(jaeger *v1.Jaeger) []batchv1.Job {
Value: port,
})

keyspace := jaeger.Spec.Storage.Options.Map()["cassandra.keyspace"]
keyspace := jaeger.Spec.Storage.Options.StringMap()["cassandra.keyspace"]
if keyspace == "" {
jaeger.Logger().Info("Cassandra keyspace not specified. Using 'jaeger_v1_test' for the cassandra-create-schema job.")
keyspace = "jaeger_v1_test" // this is default in the image
Expand All @@ -74,16 +74,18 @@ func cassandraDeps(jaeger *v1.Jaeger) []batchv1.Job {
Name: "KEYSPACE",
Value: keyspace,
})
username := jaeger.Spec.Storage.Options.StringMap()["cassandra.username"]
password := jaeger.Spec.Storage.Options.StringMap()["cassandra.password"]

envFromSource := util.CreateEnvsFromSecret(jaeger.Spec.Storage.SecretName)
if len(envFromSource) == 0 {
envVars = append(envVars, corev1.EnvVar{
Name: "CASSANDRA_USERNAME",
Value: jaeger.Spec.Storage.Options.Map()["cassandra.username"],
Value: username,
})
envVars = append(envVars, corev1.EnvVar{
Name: "CASSANDRA_PASSWORD",
Value: jaeger.Spec.Storage.Options.Map()["cassandra.password"],
Value: password,
})
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/storage/elasticsearch_dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (

// EnableRollover returns true if rollover should be enabled
func EnableRollover(spec v1.JaegerStorageSpec) bool {
useAliases := spec.Options.Map()["es.use-aliases"]
useAliases := spec.Options.StringMap()["es.use-aliases"]
return (spec.Type == v1.JaegerESStorage) && strings.EqualFold(useAliases, "true")
}

Expand Down Expand Up @@ -78,7 +78,7 @@ func envVars(opts v1.Options) []corev1.EnvVar {
{flag: "es.num-shards", envVar: "SHARDS"},
{flag: "es.num-replicas", envVar: "REPLICAS"},
}
options := opts.Map()
options := opts.StringMap()
for _, x := range scriptEnvVars {
if val, ok := options[x.flag]; ok {
envs = append(envs, corev1.EnvVar{Name: x.envVar, Value: val})
Expand Down
4 changes: 2 additions & 2 deletions pkg/strategy/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func distributedStorage(storage v1.JaegerStorageType) bool {
}

func normalizeSparkDependencies(spec *v1.JaegerStorageSpec) {
sFlagsMap := spec.Options.Map()
sFlagsMap := spec.Options.StringMap()
tlsEnabled := sFlagsMap["es.tls"]
tlsSkipHost := sFlagsMap["es.tls.skip-host-verify"]
tlsCa := sFlagsMap["es.tls.ca"]
Expand Down Expand Up @@ -201,7 +201,7 @@ func normalizeUI(spec *v1.JaegerSpec) {
uiOpts = m
}
}
enableArchiveButton(uiOpts, spec.Storage.Options.Map())
enableArchiveButton(uiOpts, spec.Storage.Options.StringMap())
disableDependenciesTab(uiOpts, spec.Storage.Type, spec.Storage.Dependencies.Enabled)
enableDocumentationLink(uiOpts, spec)
enableLogOut(uiOpts, spec)
Expand Down
2 changes: 1 addition & 1 deletion pkg/upgrade/v1_22_0_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ func TestMigrateQueryHostPortFlagsv1_22_0(t *testing.T) {
persisted := &v1.Jaeger{}
assert.NoError(t, cl.Get(context.Background(), nsn, persisted))
assert.Equal(t, latestVersion, persisted.Status.Version)
assert.Equal(t, tt.expectedOps, persisted.Spec.Query.Options.Map())
assert.Equal(t, tt.expectedOps, persisted.Spec.Query.Options.StringMap())

}

Expand Down
8 changes: 6 additions & 2 deletions pkg/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,16 @@ func Labels(name, component string, jaeger v1.Jaeger) map[string]string {
}

// GetEsHostname return first ES hostname from options map
func GetEsHostname(opts map[string]string) string {
func GetEsHostname(opts map[string]interface{}) string {
urls, ok := opts["es.server-urls"]
if !ok {
return ""
}
urlArr := strings.Split(urls, ",")
urlsString, isString := urls.(string)
if !isString {
return ""
}
urlArr := strings.Split(urlsString, ",")
return urlArr[0]
}

Expand Down
10 changes: 5 additions & 5 deletions pkg/util/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,14 @@ func TestMergeTolerations(t *testing.T) {

func TestGetEsHostname(t *testing.T) {
tests := []struct {
underTest map[string]string
underTest map[string]interface{}
hostname string
}{
{hostname: ""},
{underTest: map[string]string{"": ""}, hostname: ""},
{underTest: map[string]string{"es.server-urls": ""}, hostname: ""},
{underTest: map[string]string{"es.server-urls": "goo:tar"}, hostname: "goo:tar"},
{underTest: map[string]string{"es.server-urls": "http://es:9000,https://es2:9200"}, hostname: "http://es:9000"},
{underTest: map[string]interface{}{"": ""}, hostname: ""},
{underTest: map[string]interface{}{"es.server-urls": ""}, hostname: ""},
{underTest: map[string]interface{}{"es.server-urls": "goo:tar"}, hostname: "goo:tar"},
{underTest: map[string]interface{}{"es.server-urls": "http://es:9000,https://es2:9200"}, hostname: "http://es:9000"},
}
for _, test := range tests {
assert.Equal(t, test.hostname, GetEsHostname(test.underTest))
Expand Down