func ListMerge(listStrs []string) (string, error) {
	optionStrings := make(map[string]bool)
	for _, optionsList := range listStrs {
		brokenString := strings.Split(optionsList, ",")
		for _, option := range brokenString {
			optionStrings[strings.TrimSpace(option)] = true
		}
	}
	keys := make([]string, len(optionStrings))
	i := 0
	for key := range optionStrings {
		keys[i] = key
		i++
	}
	sort.Strings(keys)
	return strings.Join(keys, ","), nil
}
ListMerge takes a list comma separated strings and performs a set union on them.
package utils
import (
	"context"
	b64 "encoding/base64"
	"fmt"
	"io/ioutil"
	"math/rand"
	"reflect"
	"sort"
	"strconv"
	"strings"
	"time"
	"github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/errors"
	obj "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/object"
	"github.com/go-logr/logr"
	core "k8s.io/api/core/v1"
	k8serr "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
	ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
)
const rCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
const lCharSet = "abcdefghijklmnopqrstuvwxyz0123456789"
func init() {
	rand.Seed(time.Now().UnixNano())
}
var Log logr.Logger = ctrllog.NullLogger{}
func RandString(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = rCharSet[rand.Intn(len(rCharSet))]
	}
	return string(b)
}
func RandStringLower(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = lCharSet[rand.Intn(len(lCharSet))]
	}
	return string(b)
}
type Updater bool
func (u *Updater) Apply(ctx context.Context, cl client.Client, obj client.Object) error {
	var err error
	var kind string
	if obj.GetObjectKind().GroupVersionKind().Kind == "" {
		kind = reflect.TypeOf(obj).String()
	} else {
		kind = obj.GetObjectKind().GroupVersionKind().Kind
	}
	meta := obj.(metav1.Object)
	if *u {
		err = cl.Update(ctx, obj)
	} else {
		if meta.GetName() == "" {
			Log.Info("Skipping resource as name unknown", "kind", kind)
			return nil
		}
		err = cl.Create(ctx, obj)
	}
	if err != nil {
		verb := "creating"
		if *u {
			verb = "updating"
		}
		msg := fmt.Sprintf("Error %s resource %s %s", verb, kind, meta.GetName())
		return errors.Wrap(msg, err)
	}
	return nil
}
func UpdateOrErr(err error) (Updater, error) {
	update := Updater(err == nil)
	if err != nil && !k8serr.IsNotFound(err) {
		return update, errors.Wrap("Failed to update", err)
	}
	return update, nil
}
func UpdateAllOrErr(ctx context.Context, cl client.Client, nn types.NamespacedName, obj ...client.Object) (map[client.Object]Updater, error) {
	updates := map[client.Object]Updater{}
	for _, resource := range obj {
		update, err := UpdateOrErr(cl.Get(ctx, nn, resource))
		if err != nil {
			return updates, err
		}
		updates[resource] = update
	}
	return updates, nil
}
func ApplyAll(ctx context.Context, cl client.Client, updates map[client.Object]Updater) error {
	for resource, update := range updates {
		if err := update.Apply(ctx, cl, resource); err != nil {
			return err
		}
	}
	return nil
}
func B64Decode(s *core.Secret, key string) (string, error) {
	decoded, err := b64.StdEncoding.DecodeString(string(s.Data[key]))
	if err != nil {
		return "", err
	}
	return string(decoded), nil
}
func intMinMax(listStrInts []string, max bool) (string, error) {
	var listInts []int
	for _, strint := range listStrInts {
		i, err := strconv.Atoi(strint)
		if err != nil {
			return "", errors.Wrap("Failed to convert", err)
		}
		listInts = append(listInts, i)
	}
	ol := listInts[0]
	for i, e := range listInts {
		if max {
			if i == 0 || e > ol {
				ol = e
			}
		} else {
			if i == 0 || e < ol {
				ol = e
			}
		}
	}
	return strconv.Itoa(ol), nil
}
func IntMin(listStrInts []string) (string, error) {
	return intMinMax(listStrInts, false)
}
func IntMax(listStrInts []string) (string, error) {
	return intMinMax(listStrInts, true)
}