diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1562e222fa..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -language: go - -os: - - linux - - osx - -cache: - directories: - - $HOME/.cache/go-build - - $GOPATH/pkg/mod - -go: -- "1.12" - -git: - depth: 3 - -go_import_path: sigs.k8s.io/controller-runtime - -install: -- go get -u github.com/golang/dep/cmd/dep -#- go get -u golang.org/x/lint/golint -- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.15.0 - -script: -- GO111MODULE=on TRACE=1 ./hack/check-everything.sh - - -# TBD. Suppressing for now. -notifications: - email: false diff --git a/Gopkg.lock b/Gopkg.lock index 0996047409..d3d1f1dc80 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -878,11 +878,12 @@ [[projects]] branch = "master" - digest = "1:14e8a3b53e6d8cb5f44783056b71bb2ca1ac7e333939cc97f3e50b579c920845" + digest = "1:ee6063c66a2b3be97487a8df6f359cd5f38b3a0d1fc2442b49f214f24622d321" name = "k8s.io/utils" packages = [ "buffer", "integer", + "pointer", "trace", ] pruneopts = "UT" @@ -978,6 +979,7 @@ "k8s.io/client-go/tools/reference", "k8s.io/client-go/util/workqueue", "k8s.io/kube-openapi/pkg/common", + "k8s.io/utils/pointer", "sigs.k8s.io/testing_frameworks/integration", "sigs.k8s.io/testing_frameworks/integration/addr", "sigs.k8s.io/yaml", diff --git a/hack/release/release-notes.sh b/hack/release/release-notes.sh index 8074e45ddd..4f4a676388 100755 --- a/hack/release/release-notes.sh +++ b/hack/release/release-notes.sh @@ -21,7 +21,16 @@ NEWLINE=" " head_commit=$(git rev-parse HEAD) while read commit_word commit; do + if [[ -z ${commit_word} ]]; then + # skip terminating blank lines + continue + fi read title + if [[ ${title} == "Merge branch '"*"' into release-"* ]]; then + # skip temporary merge commits for calculating release notes + continue + fi + read # skip the blank line read prefix body diff --git a/pkg/client/options.go b/pkg/client/options.go index f8251ce25e..0eb04baa99 100644 --- a/pkg/client/options.go +++ b/pkg/client/options.go @@ -142,6 +142,21 @@ func (o *CreateOptions) ApplyOptions(opts []CreateOption) *CreateOptions { return o } +// ApplyToCreate implements CreateOption +func (o *CreateOptions) ApplyToCreate(co *CreateOptions) { + if o.DryRun != nil { + co.DryRun = o.DryRun + } + if o.FieldManager != "" { + co.FieldManager = o.FieldManager + } + if o.Raw != nil { + co.Raw = o.Raw + } +} + +var _ CreateOption = &CreateOptions{} + // CreateDryRunAll sets the "dry run" option to "all". // // Deprecated: Use DryRunAll @@ -211,6 +226,27 @@ func (o *DeleteOptions) ApplyOptions(opts []DeleteOption) *DeleteOptions { return o } +var _ DeleteOption = &DeleteOptions{} + +// ApplyToDelete implements DeleteOption +func (o *DeleteOptions) ApplyToDelete(do *DeleteOptions) { + if o.GracePeriodSeconds != nil { + do.GracePeriodSeconds = o.GracePeriodSeconds + } + if o.Preconditions != nil { + do.Preconditions = o.Preconditions + } + if o.PropagationPolicy != nil { + do.PropagationPolicy = o.PropagationPolicy + } + if o.Raw != nil { + do.Raw = o.Raw + } + if o.DryRun != nil { + do.DryRun = o.DryRun + } +} + // GracePeriodSeconds sets the grace period for the deletion // to the given number of seconds. type GracePeriodSeconds int64 @@ -273,6 +309,24 @@ type ListOptions struct { Raw *metav1.ListOptions } +var _ ListOption = &ListOptions{} + +// ApplyToList implements ListOption for ListOptions +func (o *ListOptions) ApplyToList(lo *ListOptions) { + if o.LabelSelector != nil { + lo.LabelSelector = o.LabelSelector + } + if o.FieldSelector != nil { + lo.FieldSelector = o.FieldSelector + } + if o.Namespace != "" { + lo.Namespace = o.Namespace + } + if o.Raw != nil { + lo.Raw = o.Raw + } +} + // AsListOptions returns these options as a flattened metav1.ListOptions. // This may mutate the Raw field. func (o *ListOptions) AsListOptions() *metav1.ListOptions { @@ -422,6 +476,21 @@ func (o *UpdateOptions) ApplyOptions(opts []UpdateOption) *UpdateOptions { return o } +var _ UpdateOption = &UpdateOptions{} + +// ApplyToUpdate implements UpdateOption +func (o *UpdateOptions) ApplyToUpdate(uo *UpdateOptions) { + if o.DryRun != nil { + uo.DryRun = o.DryRun + } + if o.FieldManager != "" { + uo.FieldManager = o.FieldManager + } + if o.Raw != nil { + uo.Raw = o.Raw + } +} + // UpdateDryRunAll sets the "dry run" option to "all". // // Deprecated: Use DryRunAll @@ -479,6 +548,24 @@ func (o *PatchOptions) AsPatchOptions() *metav1.PatchOptions { return o.Raw } +var _ PatchOption = &PatchOptions{} + +// ApplyToPatch implements PatchOptions +func (o *PatchOptions) ApplyToPatch(po *PatchOptions) { + if o.DryRun != nil { + po.DryRun = o.DryRun + } + if o.Force != nil { + po.Force = o.Force + } + if o.FieldManager != "" { + po.FieldManager = o.FieldManager + } + if o.Raw != nil { + po.Raw = o.Raw + } +} + // ForceOwnership indicates that in case of conflicts with server-side apply, // the client should acquire ownership of the conflicting field. Most // controllers should use this. @@ -518,4 +605,12 @@ func (o *DeleteAllOfOptions) ApplyOptions(opts []DeleteAllOfOption) *DeleteAllOf return o } +var _ DeleteAllOfOption = &DeleteAllOfOptions{} + +// ApplyToDeleteAllOf implements DeleteAllOfOption +func (o *DeleteAllOfOptions) ApplyToDeleteAllOf(do *DeleteAllOfOptions) { + o.ApplyToList(&do.ListOptions) + o.ApplyToDelete(&do.DeleteOptions) +} + // }}} diff --git a/pkg/client/options_test.go b/pkg/client/options_test.go new file mode 100644 index 0000000000..83b818cc5a --- /dev/null +++ b/pkg/client/options_test.go @@ -0,0 +1,205 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + utilpointer "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe("ListOptions", func() { + It("Should set LabelSelector", func() { + labelSelector, err := labels.Parse("a=b") + Expect(err).NotTo(HaveOccurred()) + o := &client.ListOptions{LabelSelector: labelSelector} + newListOpts := &client.ListOptions{} + o.ApplyToList(newListOpts) + Expect(newListOpts).To(Equal(o)) + }) + It("Should set FieldSelector", func() { + o := &client.ListOptions{FieldSelector: fields.Nothing()} + newListOpts := &client.ListOptions{} + o.ApplyToList(newListOpts) + Expect(newListOpts).To(Equal(o)) + }) + It("Should set Namespace", func() { + o := &client.ListOptions{Namespace: "my-ns"} + newListOpts := &client.ListOptions{} + o.ApplyToList(newListOpts) + Expect(newListOpts).To(Equal(o)) + }) + It("Should set Raw", func() { + o := &client.ListOptions{Raw: &metav1.ListOptions{FieldSelector: "Hans"}} + newListOpts := &client.ListOptions{} + o.ApplyToList(newListOpts) + Expect(newListOpts).To(Equal(o)) + }) + It("Should not set anything", func() { + o := &client.ListOptions{} + newListOpts := &client.ListOptions{} + o.ApplyToList(newListOpts) + Expect(newListOpts).To(Equal(o)) + }) +}) + +var _ = Describe("CreateOptions", func() { + It("Should set DryRun", func() { + o := &client.CreateOptions{DryRun: []string{"Hello", "Theodore"}} + newCreatOpts := &client.CreateOptions{} + o.ApplyToCreate(newCreatOpts) + Expect(newCreatOpts).To(Equal(o)) + }) + It("Should set FieldManager", func() { + o := &client.CreateOptions{FieldManager: "FieldManager"} + newCreatOpts := &client.CreateOptions{} + o.ApplyToCreate(newCreatOpts) + Expect(newCreatOpts).To(Equal(o)) + }) + It("Should set Raw", func() { + o := &client.CreateOptions{Raw: &metav1.CreateOptions{DryRun: []string{"Bye", "Theodore"}}} + newCreatOpts := &client.CreateOptions{} + o.ApplyToCreate(newCreatOpts) + Expect(newCreatOpts).To(Equal(o)) + }) + It("Should not set anything", func() { + o := &client.CreateOptions{} + newCreatOpts := &client.CreateOptions{} + o.ApplyToCreate(newCreatOpts) + Expect(newCreatOpts).To(Equal(o)) + }) +}) + +var _ = Describe("DeleteOptions", func() { + It("Should set GracePeriodSeconds", func() { + o := &client.DeleteOptions{GracePeriodSeconds: utilpointer.Int64Ptr(42)} + newDeleteOpts := &client.DeleteOptions{} + o.ApplyToDelete(newDeleteOpts) + Expect(newDeleteOpts).To(Equal(o)) + }) + It("Should set Preconditions", func() { + o := &client.DeleteOptions{Preconditions: &metav1.Preconditions{}} + newDeleteOpts := &client.DeleteOptions{} + o.ApplyToDelete(newDeleteOpts) + Expect(newDeleteOpts).To(Equal(o)) + }) + It("Should set PropagationPolicy", func() { + policy := metav1.DeletePropagationBackground + o := &client.DeleteOptions{PropagationPolicy: &policy} + newDeleteOpts := &client.DeleteOptions{} + o.ApplyToDelete(newDeleteOpts) + Expect(newDeleteOpts).To(Equal(o)) + }) + It("Should set Raw", func() { + o := &client.DeleteOptions{Raw: &metav1.DeleteOptions{}} + newDeleteOpts := &client.DeleteOptions{} + o.ApplyToDelete(newDeleteOpts) + Expect(newDeleteOpts).To(Equal(o)) + }) + It("Should set DryRun", func() { + o := &client.DeleteOptions{DryRun: []string{"Hello", "Pippa"}} + newDeleteOpts := &client.DeleteOptions{} + o.ApplyToDelete(newDeleteOpts) + Expect(newDeleteOpts).To(Equal(o)) + }) + It("Should not set anything", func() { + o := &client.DeleteOptions{} + newDeleteOpts := &client.DeleteOptions{} + o.ApplyToDelete(newDeleteOpts) + Expect(newDeleteOpts).To(Equal(o)) + }) +}) + +var _ = Describe("UpdateOptions", func() { + It("Should set DryRun", func() { + o := &client.UpdateOptions{DryRun: []string{"Bye", "Pippa"}} + newUpdateOpts := &client.UpdateOptions{} + o.ApplyToUpdate(newUpdateOpts) + Expect(newUpdateOpts).To(Equal(o)) + }) + It("Should set FieldManager", func() { + o := &client.UpdateOptions{FieldManager: "Hello Boris"} + newUpdateOpts := &client.UpdateOptions{} + o.ApplyToUpdate(newUpdateOpts) + Expect(newUpdateOpts).To(Equal(o)) + }) + It("Should set Raw", func() { + o := &client.UpdateOptions{Raw: &metav1.UpdateOptions{}} + newUpdateOpts := &client.UpdateOptions{} + o.ApplyToUpdate(newUpdateOpts) + Expect(newUpdateOpts).To(Equal(o)) + }) + It("Should not set anything", func() { + o := &client.UpdateOptions{} + newUpdateOpts := &client.UpdateOptions{} + o.ApplyToUpdate(newUpdateOpts) + Expect(newUpdateOpts).To(Equal(o)) + }) +}) + +var _ = Describe("PatchOptions", func() { + It("Should set DryRun", func() { + o := &client.PatchOptions{DryRun: []string{"Bye", "Boris"}} + newPatchOpts := &client.PatchOptions{} + o.ApplyToPatch(newPatchOpts) + Expect(newPatchOpts).To(Equal(o)) + }) + It("Should set Force", func() { + o := &client.PatchOptions{Force: utilpointer.BoolPtr(true)} + newPatchOpts := &client.PatchOptions{} + o.ApplyToPatch(newPatchOpts) + Expect(newPatchOpts).To(Equal(o)) + }) + It("Should set FieldManager", func() { + o := &client.PatchOptions{FieldManager: "Hello Julian"} + newPatchOpts := &client.PatchOptions{} + o.ApplyToPatch(newPatchOpts) + Expect(newPatchOpts).To(Equal(o)) + }) + It("Should set Raw", func() { + o := &client.PatchOptions{Raw: &metav1.PatchOptions{}} + newPatchOpts := &client.PatchOptions{} + o.ApplyToPatch(newPatchOpts) + Expect(newPatchOpts).To(Equal(o)) + }) + It("Should not set anything", func() { + o := &client.PatchOptions{} + newPatchOpts := &client.PatchOptions{} + o.ApplyToPatch(newPatchOpts) + Expect(newPatchOpts).To(Equal(o)) + }) +}) + +var _ = Describe("DeleteAllOfOptions", func() { + It("Should set ListOptions", func() { + o := &client.DeleteAllOfOptions{ListOptions: client.ListOptions{Raw: &metav1.ListOptions{}}} + newDeleteAllOfOpts := &client.DeleteAllOfOptions{} + o.ApplyToDeleteAllOf(newDeleteAllOfOpts) + Expect(newDeleteAllOfOpts).To(Equal(o)) + }) + It("Should set DeleleteOptions", func() { + o := &client.DeleteAllOfOptions{DeleteOptions: client.DeleteOptions{GracePeriodSeconds: utilpointer.Int64Ptr(44)}} + newDeleteAllOfOpts := &client.DeleteAllOfOptions{} + o.ApplyToDeleteAllOf(newDeleteAllOfOpts) + Expect(newDeleteAllOfOpts).To(Equal(o)) + }) +}) diff --git a/pkg/internal/recorder/recorder.go b/pkg/internal/recorder/recorder.go index 18e150594c..af230ad503 100644 --- a/pkg/internal/recorder/recorder.go +++ b/pkg/internal/recorder/recorder.go @@ -39,14 +39,13 @@ type provider struct { } // NewProvider create a new Provider instance. -func NewProvider(config *rest.Config, scheme *runtime.Scheme, logger logr.Logger) (recorder.Provider, error) { +func NewProvider(config *rest.Config, scheme *runtime.Scheme, logger logr.Logger, broadcaster record.EventBroadcaster) (recorder.Provider, error) { clientSet, err := kubernetes.NewForConfig(config) if err != nil { return nil, fmt.Errorf("failed to init clientSet: %v", err) } - p := &provider{scheme: scheme, logger: logger} - p.eventBroadcaster = record.NewBroadcaster() + p := &provider{scheme: scheme, logger: logger, eventBroadcaster: broadcaster} p.eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: clientSet.CoreV1().Events("")}) p.eventBroadcaster.StartEventWatcher( func(e *corev1.Event) { diff --git a/pkg/internal/recorder/recorder_test.go b/pkg/internal/recorder/recorder_test.go index 35bc6f1726..8aac94a5da 100644 --- a/pkg/internal/recorder/recorder_test.go +++ b/pkg/internal/recorder/recorder_test.go @@ -21,13 +21,14 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/internal/recorder" ) var _ = Describe("recorder.Provider", func() { Describe("NewProvider", func() { It("should return a provider instance and a nil error.", func() { - provider, err := recorder.NewProvider(cfg, scheme.Scheme, tlog.NullLogger{}) + provider, err := recorder.NewProvider(cfg, scheme.Scheme, tlog.NullLogger{}, record.NewBroadcaster()) Expect(provider).NotTo(BeNil()) Expect(err).NotTo(HaveOccurred()) }) @@ -36,13 +37,13 @@ var _ = Describe("recorder.Provider", func() { // Invalid the config cfg1 := *cfg cfg1.ContentType = "invalid-type" - _, err := recorder.NewProvider(&cfg1, scheme.Scheme, tlog.NullLogger{}) + _, err := recorder.NewProvider(&cfg1, scheme.Scheme, tlog.NullLogger{}, record.NewBroadcaster()) Expect(err.Error()).To(ContainSubstring("failed to init clientSet")) }) }) Describe("GetEventRecorder", func() { It("should return a recorder instance.", func() { - provider, err := recorder.NewProvider(cfg, scheme.Scheme, tlog.NullLogger{}) + provider, err := recorder.NewProvider(cfg, scheme.Scheme, tlog.NullLogger{}, record.NewBroadcaster()) Expect(err).NotTo(HaveOccurred()) recorder := provider.GetEventRecorderFor("test") diff --git a/pkg/manager/internal.go b/pkg/manager/internal.go index ed4ae806e3..5e5a2611e0 100644 --- a/pkg/manager/internal.go +++ b/pkg/manager/internal.go @@ -111,6 +111,10 @@ type controllerManager struct { port int // host is the hostname that the webhook server binds to. host string + // CertDir is the directory that contains the server key and certificate. + // if not set, webhook server would look up the server key and certificate in + // {TempDir}/k8s-webhook-server/serving-certs + certDir string webhookServer *webhook.Server @@ -219,8 +223,9 @@ func (cm *controllerManager) GetAPIReader() client.Reader { func (cm *controllerManager) GetWebhookServer() *webhook.Server { if cm.webhookServer == nil { cm.webhookServer = &webhook.Server{ - Port: cm.port, - Host: cm.host, + Port: cm.port, + Host: cm.host, + CertDir: cm.certDir, } if err := cm.Add(cm.webhookServer); err != nil { panic("unable to add webhookServer to the controller manager") diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 56a34cba6e..2d045fa91d 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -149,6 +149,10 @@ type Options struct { // It is used to set webhook.Server.Host. Host string + // CertDir is the directory that contains the server key and certificate. + // if not set, webhook server would look up the server key and certificate in + // {TempDir}/k8s-webhook-server/serving-certs + CertDir string // Functions to all for a user to customize the values that will be injected. // NewCache is the function that will create the cache to be used @@ -160,8 +164,12 @@ type Options struct { // use the cache for reads and the client for writes. NewClient NewClientFunc + // EventBroadcaster records Events emitted by the manager and sends them to the Kubernetes API + // Use this to customize the event correlator and spam filter + EventBroadcaster record.EventBroadcaster + // Dependency injection for testing - newRecorderProvider func(config *rest.Config, scheme *runtime.Scheme, logger logr.Logger) (recorder.Provider, error) + newRecorderProvider func(config *rest.Config, scheme *runtime.Scheme, logger logr.Logger, broadcaster record.EventBroadcaster) (recorder.Provider, error) newResourceLock func(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error) newMetricsListener func(addr string) (net.Listener, error) } @@ -231,7 +239,7 @@ func New(config *rest.Config, options Options) (Manager, error) { // Create the recorder provider to inject event recorders for the components. // TODO(directxman12): the log for the event provider should have a context (name, tags, etc) specific // to the particular controller that it's being injected into, rather than a generic one like is here. - recorderProvider, err := options.newRecorderProvider(config, options.Scheme, log.WithName("events")) + recorderProvider, err := options.newRecorderProvider(config, options.Scheme, log.WithName("events"), options.EventBroadcaster) if err != nil { return nil, err } @@ -271,6 +279,7 @@ func New(config *rest.Config, options Options) (Manager, error) { internalStopper: stop, port: options.Port, host: options.Host, + certDir: options.CertDir, leaseDuration: *options.LeaseDuration, renewDeadline: *options.RenewDeadline, retryPeriod: *options.RetryPeriod, @@ -342,5 +351,9 @@ func setOptionsDefaults(options Options) Options { options.RetryPeriod = &retryPeriod } + if options.EventBroadcaster == nil { + options.EventBroadcaster = record.NewBroadcaster() + } + return options } diff --git a/pkg/manager/manager_test.go b/pkg/manager/manager_test.go index 7819f04955..de771c85b5 100644 --- a/pkg/manager/manager_test.go +++ b/pkg/manager/manager_test.go @@ -30,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" "k8s.io/client-go/tools/leaderelection/resourcelock" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/cache/informertest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -111,7 +112,7 @@ var _ = Describe("manger.Manager", func() { It("should return an error it can't create a recorder.Provider", func(done Done) { m, err := New(cfg, Options{ - newRecorderProvider: func(config *rest.Config, scheme *runtime.Scheme, logger logr.Logger) (recorder.Provider, error) { + newRecorderProvider: func(config *rest.Config, scheme *runtime.Scheme, logger logr.Logger, broadcaster record.EventBroadcaster) (recorder.Provider, error) { return nil, fmt.Errorf("expected error") }, })