Skip to content

Commit

Permalink
Allow multiple watchers per namespace
Browse files Browse the repository at this point in the history
* Separate watcher will be created per Namespace
* Adapt the tests
* Resolve merge conflicts
  • Loading branch information
muralov committed Oct 16, 2023
1 parent 562808a commit 8ef615b
Show file tree
Hide file tree
Showing 20 changed files with 145 additions and 103 deletions.
1 change: 0 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/healthz"

apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextensionsv1clientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"

apigatewayv1beta1 "github.com/kyma-incubator/api-gateway/api/v1beta1"
eventingv1alpha1 "github.com/kyma-project/eventing-manager/api/v1alpha1"
Expand Down
23 changes: 0 additions & 23 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ module github.com/kyma-project/eventing-manager

go 1.21

toolchain go1.21.1

require (
github.com/avast/retry-go/v3 v3.1.1
github.com/cloudevents/sdk-go/protocol/nats/v2 v2.14.0
Expand All @@ -21,9 +19,6 @@ require (
github.com/nats-io/nats.go v1.28.0
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.27.10
github.com/kyma-project/kyma/components/eventing-controller v0.0.0-20231005133034-b2a8848645e0
github.com/kyma-project/nats-manager v1.0.2-0.20231013094902-6091000ea996
github.com/onsi/gomega v1.27.10
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.16.0
github.com/stretchr/testify v1.8.4
Expand All @@ -37,13 +32,6 @@ require (
k8s.io/client-go v0.28.1
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2
sigs.k8s.io/controller-runtime v0.15.2
go.uber.org/zap v1.25.0
k8s.io/api v0.28.1
k8s.io/apiextensions-apiserver v0.28.0
k8s.io/apimachinery v0.28.1
k8s.io/client-go v0.28.1
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2
sigs.k8s.io/controller-runtime v0.15.2
)

require (
Expand All @@ -67,8 +55,6 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/kyma-project/kyma/common/logging v0.0.0-20230616102347-ad6927dc28d5 // indirect
github.com/kyma-project/kyma/components/application-operator v0.0.0-20230127165033-ec8e43477eca // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
Expand All @@ -77,8 +63,6 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nats-io/jwt/v2 v2.4.1 // indirect
github.com/nats-io/nats-server/v2 v2.9.21 // indirect
github.com/nats-io/nats.go v1.28.0 // indirect
github.com/nats-io/nkeys v0.4.4 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/nxadm/tail v1.4.8 // indirect
Expand All @@ -89,20 +73,13 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/net v0.13.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/net v0.13.0 // indirect
golang.org/x/oauth2 v0.10.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/term v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.3.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
Expand Down
14 changes: 12 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ github.com/avast/retry-go/v3 v3.1.1 h1:49Scxf4v8PmiQ/nY0aY3p0hDueqSmc7++cBbtiDGu
github.com/avast/retry-go/v3 v3.1.1/go.mod h1:6cXRK369RpzFL3UQGqIUp9Q7GDrams+KsYWrfNA1/nQ=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudevents/sdk-go/protocol/nats/v2 v2.14.0 h1:cPOXwhwRb+RtHrPSs6Qmobgt4q/0e4wNBdfUjOeV9Qw=
Expand Down Expand Up @@ -38,6 +40,7 @@ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
Expand Down Expand Up @@ -65,6 +68,7 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
Expand All @@ -84,6 +88,7 @@ github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQs
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand All @@ -96,8 +101,8 @@ github.com/kyma-project/kyma/components/application-operator v0.0.0-202301271650
github.com/kyma-project/kyma/components/application-operator v0.0.0-20230127165033-ec8e43477eca/go.mod h1:Tog02gZ1VT7yvFmhSqmiuGZpDYt18zTF4kr6E0N9ttk=
github.com/kyma-project/kyma/components/eventing-controller v0.0.0-20231006112231-696ba866d5ec h1:P+V9r14NJKUFbIZIEMrjInABT+qN/ySLqWQ01aeq068=
github.com/kyma-project/kyma/components/eventing-controller v0.0.0-20231006112231-696ba866d5ec/go.mod h1:Ht1hT95iSKe5md3QhIZQrq3j9WXkiWe/qdDMaDrmdyI=
github.com/kyma-project/nats-manager v0.0.0-20230718133808-9241d3b926bd h1:oZboLZkGT/1wP/lNvuqeJPOwslHS+Oq0BGuiS0L0o2M=
github.com/kyma-project/nats-manager v0.0.0-20230718133808-9241d3b926bd/go.mod h1:KCs/xWuIyxSQSckDuHUxH60xiDD67xgPtfqHujzfqbw=
github.com/kyma-project/nats-manager v1.0.2-0.20231013094902-6091000ea996 h1:BDN7EF2vo8Cn4ciDxB5ZQFIfwS1Ciis8CEPYK0b11ho=
github.com/kyma-project/nats-manager v1.0.2-0.20231013094902-6091000ea996/go.mod h1:gC0W/C2NgpmVnG/5v5f76ijNw5aloQLEkSuVOElB9gk=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
Expand Down Expand Up @@ -131,6 +136,7 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
Expand All @@ -149,6 +155,7 @@ github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand All @@ -164,6 +171,7 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
Expand All @@ -172,6 +180,7 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
Expand Down Expand Up @@ -240,6 +249,7 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
34 changes: 20 additions & 14 deletions internal/controller/eventing/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ type Reconciler struct {
allowedEventingCR *eventingv1alpha1.Eventing
clusterScopedResourcesWatched bool
natsCRWatchStarted bool
natsWatcher watcher.Watcher
natsWatchers map[string]watcher.Watcher
}

func NewReconciler(
Expand Down Expand Up @@ -124,6 +124,7 @@ func NewReconciler(
isNATSSubManagerStarted: false,
natsConfigHandler: NewNatsConfigHandler(kubeClient, opts),
allowedEventingCR: allowedEventingCR,
natsWatchers: make(map[string]watcher.Watcher),
}
}

Expand Down Expand Up @@ -257,16 +258,17 @@ func (r *Reconciler) watchResource(kind client.Object, eventing *eventingv1alpha
}

func (r *Reconciler) startNatsCRWatch(eventing *eventingv1alpha1.Eventing) error {
if r.natsCRWatchStarted {
natsWatcher, found := r.natsWatchers[eventing.Namespace]
if found && natsWatcher.IsStarted() {
return nil
}

if r.natsWatcher == nil {
r.natsWatcher = watcher.NewResourceWatcher(r.dynamicClient, k8s.NatsGVK, eventing.Namespace)
if !found {
natsWatcher = watcher.NewResourceWatcher(r.dynamicClient, k8s.NatsGVK, eventing.Namespace)
r.natsWatchers[eventing.Namespace] = natsWatcher
}

r.natsWatcher.Start()
if err := r.controller.Watch(&source.Channel{Source: r.natsWatcher.GetEventsChannel()},
if err := r.controller.Watch(&source.Channel{Source: natsWatcher.GetEventsChannel()},
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
return []reconcile.Request{
{NamespacedName: types.NamespacedName{
Expand All @@ -280,13 +282,17 @@ func (r *Reconciler) startNatsCRWatch(eventing *eventingv1alpha1.Eventing) error
r.natsCRWatchStarted = false
return err
}
r.natsCRWatchStarted = true
natsWatcher.Start()

return nil
}

func (r *Reconciler) stopNatsCRWatch() {
r.natsWatcher.Stop()
r.natsWatcher = nil
func (r *Reconciler) stopNatsCRWatch(eventing *eventingv1alpha1.Eventing) {
natsWatcher, found := r.natsWatchers[eventing.Namespace]
if found {
natsWatcher.Stop()
delete(r.natsWatchers, eventing.Namespace)
}
r.natsCRWatchStarted = false
}

Expand Down Expand Up @@ -407,7 +413,7 @@ func (r *Reconciler) handleBackendSwitching(
if err := r.stopNATSSubManager(true, log); err != nil {
return err
}
r.stopNatsCRWatch()
r.stopNatsCRWatch(eventing)
} else if eventing.Status.ActiveBackend == eventingv1alpha1.EventMeshBackendType {
if err := r.stopEventMeshSubManager(true, log); err != nil {
return err
Expand All @@ -421,7 +427,7 @@ func (r *Reconciler) handleBackendSwitching(
}

func (r *Reconciler) reconcileNATSBackend(ctx context.Context, eventing *eventingv1alpha1.Eventing, log *zap.SugaredLogger) (ctrl.Result, error) {
_, err := r.kubeClient.GetCRD(k8s.NatsGVK.GroupResource().String())
_, err := r.kubeClient.GetCRD(ctx, k8s.NatsGVK.GroupResource().String())
if err != nil {
if apierrors.IsNotFound(err) {
err = fmt.Errorf("NATS module has to be installed: %v", err)
Expand All @@ -430,12 +436,12 @@ func (r *Reconciler) reconcileNATSBackend(ctx context.Context, eventing *eventin
return ctrl.Result{}, err
}

if err := r.startNatsCRWatch(eventing); err != nil {
if err = r.startNatsCRWatch(eventing); err != nil {
return ctrl.Result{}, r.syncStatusWithNATSErr(ctx, eventing, err, log)
}

// check nats CR if it exists and is in natsAvailable state
err := r.checkNATSAvailability(ctx, eventing)
err = r.checkNATSAvailability(ctx, eventing)
if err != nil {
return ctrl.Result{}, r.syncStatusWithNATSErr(ctx, eventing, err, log)
}
Expand Down
Loading

0 comments on commit 8ef615b

Please sign in to comment.