mirror of https://github.com/grafana/loki
LOG-1251/convert to statefulsets (#16)
parent
b31c3316fe
commit
2ee3b4649c
@ -1,23 +1,51 @@ |
||||
package manifests |
||||
|
||||
import ( |
||||
"github.com/ViaQ/logerr/kverrors" |
||||
"github.com/creasty/defaults" |
||||
"sigs.k8s.io/controller-runtime/pkg/client" |
||||
) |
||||
|
||||
// BuildAll builds all manifests required to run a Loki Stack
|
||||
func BuildAll(opt Options) ([]client.Object, error) { |
||||
if err := setDefaultOptions(&opt); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
res := make([]client.Object, 0) |
||||
|
||||
cm, err := LokiConfigMap(opt.Name, opt.Namespace) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
res = append(res, cm) |
||||
res = append(res, BuildDistributor(opt.Name)...) |
||||
res = append(res, BuildIngester(opt.Name)...) |
||||
res = append(res, BuildQuerier(opt.Name)...) |
||||
|
||||
ingesterObjects, err := BuildIngester(opt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
res = append(res, ingesterObjects...) |
||||
|
||||
querierObjects, err := BuildQuerier(opt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
res = append(res, querierObjects...) |
||||
|
||||
res = append(res, BuildQueryFrontend(opt.Name)...) |
||||
res = append(res, LokiGossipRingService(opt.Name)) |
||||
|
||||
return res, nil |
||||
} |
||||
|
||||
func setDefaultOptions(o *Options) error { |
||||
if err := defaults.Set(o); err != nil { |
||||
// I believe this is only caused when the default tag has an incorrect type such as Field int `default:"hello"`
|
||||
// so it shouldn't happen in production
|
||||
return kverrors.Wrap(err, "could not set default options") |
||||
} |
||||
// TODO add some complex defaults here that the vendored package does not support
|
||||
return nil |
||||
} |
||||
|
@ -0,0 +1,31 @@ |
||||
package manifests_test |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
lokiv1beta1 "github.com/ViaQ/loki-operator/api/v1beta1" |
||||
"github.com/ViaQ/loki-operator/internal/manifests" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestNewIngesterStatefulSet_SelectorMatchesLabels(t *testing.T) { |
||||
// You must set the .spec.selector field of a StatefulSet to match the labels of
|
||||
// its .spec.template.metadata.labels. Prior to Kubernetes 1.8, the
|
||||
// .spec.selector field was defaulted when omitted. In 1.8 and later versions,
|
||||
// failing to specify a matching Pod Selector will result in a validation error
|
||||
// during StatefulSet creation.
|
||||
// See https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#pod-selector
|
||||
ss := manifests.NewIngesterStatefulSet(manifests.Options{ |
||||
Name: "abcd", |
||||
Namespace: "efgh", |
||||
Stack: lokiv1beta1.LokiStackSpec{ |
||||
StorageClassName: "standard", |
||||
}, |
||||
}) |
||||
|
||||
l := ss.Spec.Template.GetObjectMeta().GetLabels() |
||||
for key, value := range ss.Spec.Selector.MatchLabels { |
||||
require.Contains(t, l, key) |
||||
require.Equal(t, l[key], value) |
||||
} |
||||
} |
@ -1,9 +1,15 @@ |
||||
package manifests |
||||
|
||||
import ( |
||||
lokiv1beta1 "github.com/ViaQ/loki-operator/api/v1beta1" |
||||
) |
||||
|
||||
// Options is a set of options to use when building manifests such as resource sizes, etc.
|
||||
// Most of this should be provided - either directly or indirectly - by the user. This will
|
||||
// probably be converted from the CR.
|
||||
type Options struct { |
||||
Name string |
||||
Namespace string |
||||
|
||||
Stack lokiv1beta1.LokiStackSpec |
||||
} |
||||
|
@ -0,0 +1,31 @@ |
||||
package manifests_test |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
lokiv1beta1 "github.com/ViaQ/loki-operator/api/v1beta1" |
||||
"github.com/ViaQ/loki-operator/internal/manifests" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestNewQuerierStatefulSet_SelectorMatchesLabels(t *testing.T) { |
||||
// You must set the .spec.selector field of a StatefulSet to match the labels of
|
||||
// its .spec.template.metadata.labels. Prior to Kubernetes 1.8, the
|
||||
// .spec.selector field was defaulted when omitted. In 1.8 and later versions,
|
||||
// failing to specify a matching Pod Selector will result in a validation error
|
||||
// during StatefulSet creation.
|
||||
// See https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#pod-selector
|
||||
ss := manifests.NewQuerierStatefulSet(manifests.Options{ |
||||
Name: "abcd", |
||||
Namespace: "efgh", |
||||
Stack: lokiv1beta1.LokiStackSpec{ |
||||
StorageClassName: "standard", |
||||
}, |
||||
}) |
||||
|
||||
l := ss.Spec.Template.GetObjectMeta().GetLabels() |
||||
for key, value := range ss.Spec.Selector.MatchLabels { |
||||
require.Contains(t, l, key) |
||||
require.Equal(t, l[key], value) |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
.DS_Store |
@ -0,0 +1,20 @@ |
||||
language: go |
||||
go: 1.13 |
||||
|
||||
branches: |
||||
only: |
||||
- master |
||||
|
||||
env: |
||||
global: |
||||
# CODECOV_TOKEN |
||||
- secure: "O4Hay0TPFW2eq1+Y9vlW4W8VIY9QlKzGgEoL8mgbHQ67RrvqaB7Ft6kdmUaVztu9a5ZB4QwwU8dCFV6D/+dY710qqayQghY2r4igqExzkMyRa3InQtGjhYd9CVpjbR5cXYV1PL9h/Jf3PTTUCmSJp0tpqZCce4BezgnhdSjccGzx3wlzj66+bDdpCzRkWtCvYvI4Fxg5aM5kBke1Ti+aLdEQNzDpkbM38/iyUQDuM+y6ZO+AuP9zqdMY82O6yEMXJppPtVnfqhTJQyDiEF6J6h3TauOb9nKhu8Eg0d2b0HseTRVcCigH3usCm6WYmqhInmsbdAge9gSs9SdYq06VYghG/AvnuRNbmGn3DHuRelyH0gBuXrzZxNP9nfPego4bvk6jQvPSu/flB7JHixkDkFBCO1b2R3ZdOdT6fg+qPQBMxEGy3YJ6ylZ0oqC0AByC+0OP7Hc/xv5U4uqnJ5oBVt4yt26sEjlMs8piXnkoNDoXVjgjfpqLLMyuSRzL1nbf7P270E7L0hDzWInObWlM7FuAl6ghb4j20jvx+4C5UKb8JJPMdbEOcDWZuJOnpThpcNIeP08Xks1wDJ5R01cxK2gja8Hmg+nF320bHybn3RRyOke7tuC3Egez2QaKEoQl9YmcepO+TENcsFk86v8Le68UHi8mJps5mm7iuJb2xyQ=" |
||||
|
||||
before_install: |
||||
- go get -u golang.org/x/lint/golint |
||||
|
||||
script: |
||||
- make ci-test |
||||
|
||||
after_success: |
||||
- bash <(curl -s https://codecov.io/bash) |
@ -0,0 +1,22 @@ |
||||
Copyright (c) 2017-present Yuki Iwanaga |
||||
|
||||
MIT License |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining |
||||
a copy of this software and associated documentation files (the |
||||
"Software"), to deal in the Software without restriction, including |
||||
without limitation the rights to use, copy, modify, merge, publish, |
||||
distribute, sublicense, and/or sell copies of the Software, and to |
||||
permit persons to whom the Software is furnished to do so, subject to |
||||
the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be |
||||
included in all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,30 @@ |
||||
SHELL := /bin/bash -eu -o pipefail
|
||||
|
||||
GO_TEST_FLAGS := -v
|
||||
|
||||
PACKAGE_DIRS := $(shell go list ./... 2> /dev/null | grep -v /vendor/)
|
||||
SRC_FILES := $(shell find . -name '*.go' -not -path './vendor/*')
|
||||
|
||||
|
||||
# Tasks
|
||||
#-----------------------------------------------
|
||||
.PHONY: lint |
||||
lint: |
||||
@gofmt -e -d -s $(SRC_FILES) | awk '{ e = 1; print $0 } END { if (e) exit(1) }'
|
||||
@echo $(SRC_FILES) | xargs -n1 golint -set_exit_status
|
||||
@go vet $(PACKAGE_DIRS)
|
||||
|
||||
.PHONY: test |
||||
test: lint |
||||
@go test $(GO_TEST_FLAGS) $(PACKAGE_DIRS)
|
||||
|
||||
.PHONY: ci-test |
||||
ci-test: lint |
||||
@echo > coverage.txt
|
||||
@for d in $(PACKAGE_DIRS); do \
|
||||
go test -coverprofile=profile.out -covermode=atomic -race -v $$d; \
|
||||
if [ -f profile.out ]; then \
|
||||
cat profile.out >> coverage.txt; \
|
||||
rm profile.out; \
|
||||
fi; \
|
||||
done
|
@ -0,0 +1,69 @@ |
||||
defaults |
||||
======== |
||||
|
||||
[](https://travis-ci.org/creasty/defaults) |
||||
[](https://codecov.io/gh/creasty/defaults) |
||||
[](https://github.com/creasty/defaults/releases) |
||||
[](./LICENSE) |
||||
|
||||
Initialize structs with default values |
||||
|
||||
- Supports almost all kind of types |
||||
- Scalar types |
||||
- `int/8/16/32/64`, `uint/8/16/32/64`, `float32/64` |
||||
- `uintptr`, `bool`, `string` |
||||
- Complex types |
||||
- `map`, `slice`, `struct` |
||||
- Aliased types |
||||
- `time.Duration` |
||||
- e.g., `type Enum string` |
||||
- Pointer types |
||||
- e.g., `*SampleStruct`, `*int` |
||||
- Recursively initializes fields in a struct |
||||
- Dynamically sets default values by [`defaults.Setter`](./setter.go) interface |
||||
- Preserves non-initial values from being reset with a default value |
||||
|
||||
|
||||
Usage |
||||
----- |
||||
|
||||
```go |
||||
type Gender string |
||||
|
||||
type Sample struct { |
||||
Name string `default:"John Smith"` |
||||
Age int `default:"27"` |
||||
Gender Gender `default:"m"` |
||||
|
||||
Slice []string `default:"[]"` |
||||
SliceByJSON []int `default:"[1, 2, 3]"` // Supports JSON |
||||
Map map[string]int `default:"{}"` |
||||
MapByJSON map[string]int `default:"{\"foo\": 123}"` |
||||
|
||||
Struct OtherStruct `default:"{}"` |
||||
StructPtr *OtherStruct `default:"{\"Foo\": 123}"` |
||||
|
||||
NoTag OtherStruct // Recurses into a nested struct by default |
||||
OptOut OtherStruct `default:"-"` // Opt-out |
||||
} |
||||
|
||||
type OtherStruct struct { |
||||
Hello string `default:"world"` // Tags in a nested struct also work |
||||
Foo int `default:"-"` |
||||
Random int `default:"-"` |
||||
} |
||||
|
||||
// SetDefaults implements defaults.Setter interface |
||||
func (s *OtherStruct) SetDefaults() { |
||||
if defaults.CanUpdate(s.Random) { // Check if it's a zero value (recommended) |
||||
s.Random = rand.Int() // Set a dynamic value |
||||
} |
||||
} |
||||
``` |
||||
|
||||
```go |
||||
obj := &Sample{} |
||||
if err := defaults.Set(obj); err != nil { |
||||
panic(err) |
||||
} |
||||
``` |
@ -0,0 +1,195 @@ |
||||
package defaults |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"errors" |
||||
"reflect" |
||||
"strconv" |
||||
"time" |
||||
) |
||||
|
||||
var ( |
||||
errInvalidType = errors.New("not a struct pointer") |
||||
) |
||||
|
||||
const ( |
||||
fieldName = "default" |
||||
) |
||||
|
||||
// Set initializes members in a struct referenced by a pointer.
|
||||
// Maps and slices are initialized by `make` and other primitive types are set with default values.
|
||||
// `ptr` should be a struct pointer
|
||||
func Set(ptr interface{}) error { |
||||
if reflect.TypeOf(ptr).Kind() != reflect.Ptr { |
||||
return errInvalidType |
||||
} |
||||
|
||||
v := reflect.ValueOf(ptr).Elem() |
||||
t := v.Type() |
||||
|
||||
if t.Kind() != reflect.Struct { |
||||
return errInvalidType |
||||
} |
||||
|
||||
for i := 0; i < t.NumField(); i++ { |
||||
if defaultVal := t.Field(i).Tag.Get(fieldName); defaultVal != "-" { |
||||
if err := setField(v.Field(i), defaultVal); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
callSetter(ptr) |
||||
return nil |
||||
} |
||||
|
||||
// MustSet function is a wrapper of Set function
|
||||
// It will call Set and panic if err not equals nil.
|
||||
func MustSet(ptr interface{}) { |
||||
if err := Set(ptr); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
||||
|
||||
func setField(field reflect.Value, defaultVal string) error { |
||||
if !field.CanSet() { |
||||
return nil |
||||
} |
||||
|
||||
if !shouldInitializeField(field, defaultVal) { |
||||
return nil |
||||
} |
||||
|
||||
if isInitialValue(field) { |
||||
switch field.Kind() { |
||||
case reflect.Bool: |
||||
if val, err := strconv.ParseBool(defaultVal); err == nil { |
||||
field.Set(reflect.ValueOf(val).Convert(field.Type())) |
||||
} |
||||
case reflect.Int: |
||||
if val, err := strconv.ParseInt(defaultVal, 0, strconv.IntSize); err == nil { |
||||
field.Set(reflect.ValueOf(int(val)).Convert(field.Type())) |
||||
} |
||||
case reflect.Int8: |
||||
if val, err := strconv.ParseInt(defaultVal, 0, 8); err == nil { |
||||
field.Set(reflect.ValueOf(int8(val)).Convert(field.Type())) |
||||
} |
||||
case reflect.Int16: |
||||
if val, err := strconv.ParseInt(defaultVal, 0, 16); err == nil { |
||||
field.Set(reflect.ValueOf(int16(val)).Convert(field.Type())) |
||||
} |
||||
case reflect.Int32: |
||||
if val, err := strconv.ParseInt(defaultVal, 0, 32); err == nil { |
||||
field.Set(reflect.ValueOf(int32(val)).Convert(field.Type())) |
||||
} |
||||
case reflect.Int64: |
||||
if val, err := time.ParseDuration(defaultVal); err == nil { |
||||
field.Set(reflect.ValueOf(val).Convert(field.Type())) |
||||
} else if val, err := strconv.ParseInt(defaultVal, 0, 64); err == nil { |
||||
field.Set(reflect.ValueOf(val).Convert(field.Type())) |
||||
} |
||||
case reflect.Uint: |
||||
if val, err := strconv.ParseUint(defaultVal, 0, strconv.IntSize); err == nil { |
||||
field.Set(reflect.ValueOf(uint(val)).Convert(field.Type())) |
||||
} |
||||
case reflect.Uint8: |
||||
if val, err := strconv.ParseUint(defaultVal, 0, 8); err == nil { |
||||
field.Set(reflect.ValueOf(uint8(val)).Convert(field.Type())) |
||||
} |
||||
case reflect.Uint16: |
||||
if val, err := strconv.ParseUint(defaultVal, 0, 16); err == nil { |
||||
field.Set(reflect.ValueOf(uint16(val)).Convert(field.Type())) |
||||
} |
||||
case reflect.Uint32: |
||||
if val, err := strconv.ParseUint(defaultVal, 0, 32); err == nil { |
||||
field.Set(reflect.ValueOf(uint32(val)).Convert(field.Type())) |
||||
} |
||||
case reflect.Uint64: |
||||
if val, err := strconv.ParseUint(defaultVal, 0, 64); err == nil { |
||||
field.Set(reflect.ValueOf(val).Convert(field.Type())) |
||||
} |
||||
case reflect.Uintptr: |
||||
if val, err := strconv.ParseUint(defaultVal, 0, strconv.IntSize); err == nil { |
||||
field.Set(reflect.ValueOf(uintptr(val)).Convert(field.Type())) |
||||
} |
||||
case reflect.Float32: |
||||
if val, err := strconv.ParseFloat(defaultVal, 32); err == nil { |
||||
field.Set(reflect.ValueOf(float32(val)).Convert(field.Type())) |
||||
} |
||||
case reflect.Float64: |
||||
if val, err := strconv.ParseFloat(defaultVal, 64); err == nil { |
||||
field.Set(reflect.ValueOf(val).Convert(field.Type())) |
||||
} |
||||
case reflect.String: |
||||
field.Set(reflect.ValueOf(defaultVal).Convert(field.Type())) |
||||
|
||||
case reflect.Slice: |
||||
ref := reflect.New(field.Type()) |
||||
ref.Elem().Set(reflect.MakeSlice(field.Type(), 0, 0)) |
||||
if defaultVal != "" && defaultVal != "[]" { |
||||
if err := json.Unmarshal([]byte(defaultVal), ref.Interface()); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
field.Set(ref.Elem().Convert(field.Type())) |
||||
case reflect.Map: |
||||
ref := reflect.New(field.Type()) |
||||
ref.Elem().Set(reflect.MakeMap(field.Type())) |
||||
if defaultVal != "" && defaultVal != "{}" { |
||||
if err := json.Unmarshal([]byte(defaultVal), ref.Interface()); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
field.Set(ref.Elem().Convert(field.Type())) |
||||
case reflect.Struct: |
||||
if defaultVal != "" && defaultVal != "{}" { |
||||
if err := json.Unmarshal([]byte(defaultVal), field.Addr().Interface()); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
case reflect.Ptr: |
||||
field.Set(reflect.New(field.Type().Elem())) |
||||
} |
||||
} |
||||
|
||||
switch field.Kind() { |
||||
case reflect.Ptr: |
||||
setField(field.Elem(), defaultVal) |
||||
callSetter(field.Interface()) |
||||
case reflect.Struct: |
||||
if err := Set(field.Addr().Interface()); err != nil { |
||||
return err |
||||
} |
||||
case reflect.Slice: |
||||
for j := 0; j < field.Len(); j++ { |
||||
if err := setField(field.Index(j), defaultVal); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func isInitialValue(field reflect.Value) bool { |
||||
return reflect.DeepEqual(reflect.Zero(field.Type()).Interface(), field.Interface()) |
||||
} |
||||
|
||||
func shouldInitializeField(field reflect.Value, tag string) bool { |
||||
switch field.Kind() { |
||||
case reflect.Struct: |
||||
return true |
||||
case reflect.Ptr: |
||||
if !field.IsNil() && field.Elem().Kind() == reflect.Struct { |
||||
return true |
||||
} |
||||
case reflect.Slice: |
||||
return field.Len() > 0 || tag != "" |
||||
} |
||||
|
||||
return tag != "" |
||||
} |
||||
|
||||
// CanUpdate returns true when the given value is an initial value of its type
|
||||
func CanUpdate(v interface{}) bool { |
||||
return isInitialValue(reflect.ValueOf(v)) |
||||
} |
@ -0,0 +1,3 @@ |
||||
module github.com/creasty/defaults |
||||
|
||||
go 1.13 |
@ -0,0 +1,12 @@ |
||||
package defaults |
||||
|
||||
// Setter is an interface for setting default values
|
||||
type Setter interface { |
||||
SetDefaults() |
||||
} |
||||
|
||||
func callSetter(v interface{}) { |
||||
if ds, ok := v.(Setter); ok { |
||||
ds.SetDefaults() |
||||
} |
||||
} |
Loading…
Reference in new issue