From 06438e2f03c9856830d83691a957a1d3d2440f3f Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Wed, 1 Aug 2018 11:06:36 -0700 Subject: [PATCH 01/13] Starting to add webhook. --- cmd/webhook/main.go | 74 ++++-- pkg/logging/OWNERS | 6 + pkg/logging/config.go | 61 +++++ pkg/logging/config_test.go | 276 +++++++++++++++++++++++ pkg/logging/logger.go | 38 ++++ pkg/logging/logkey/constants.go | 34 +++ pkg/logging/testdata/config-logging.yaml | 52 +++++ 7 files changed, 525 insertions(+), 16 deletions(-) create mode 100644 pkg/logging/OWNERS create mode 100644 pkg/logging/config.go create mode 100644 pkg/logging/config_test.go create mode 100644 pkg/logging/logger.go create mode 100644 pkg/logging/logkey/constants.go create mode 100644 pkg/logging/testdata/config-logging.yaml diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index 30db27e6137..1c4c12cf350 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -1,5 +1,6 @@ /* -Copyright 2018 The Knative Authors +Copyright 2017 The Knative 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 @@ -17,45 +18,86 @@ package main import ( "flag" + "log" + + "go.uber.org/zap" + + "github.com/knative/pkg/configmap" + "github.com/knative/pkg/logging" + "github.com/knative/pkg/logging/logkey" + "github.com/knative/pkg/signals" + "github.com/knative/pkg/webhook" - "github.com/golang/glog" "github.com/knative/eventing/pkg/system" - "github.com/knative/eventing/pkg/signals" - "github.com/knative/eventing/pkg/webhook" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) +const ( + logLevelKey = "webhook" +) + func main() { flag.Parse() - defer glog.Flush() + cm, err := configmap.Load("/etc/config-logging") + if err != nil { + log.Fatalf("Error loading logging configuration: %v", err) + } + config, err := logging.NewConfigFromMap(cm) + if err != nil { + log.Fatalf("Error parsing logging configuration: %v", err) + } + logger, atomicLevel := logging.NewLoggerFromConfig(config, logLevelKey) + defer logger.Sync() + logger = logger.With(zap.String(logkey.ControllerType, "webhook")) - glog.Info("Starting the Configuration Webhook") + logger.Info("Starting the Eventing Webhook") // set up signals so we handle the first shutdown signal gracefully stopCh := signals.SetupSignalHandler() clusterConfig, err := rest.InClusterConfig() if err != nil { - glog.Fatal("Failed to get in cluster config", err) + logger.Fatal("Failed to get in cluster config", zap.Error(err)) } - clientset, err := kubernetes.NewForConfig(clusterConfig) + kubeClient, err := kubernetes.NewForConfig(clusterConfig) if err != nil { - glog.Fatal("Failed to get the client set", err) + logger.Fatal("Failed to get the client set", zap.Error(err)) + } + + // Watch the logging config map and dynamically update logging levels. + configMapWatcher := configmap.NewDefaultWatcher(kubeClient, system.Namespace) + configMapWatcher.Watch(logging.ConfigName, logging.UpdateLevelFromConfigMap(logger, atomicLevel, logLevelKey)) + if err = configMapWatcher.Start(stopCh); err != nil { + logger.Fatalf("failed to start configuration manager: %v", err) } options := webhook.ControllerOptions{ - ServiceName: "eventing-webhook", - ServiceNamespace: system.Namespace, - Port: 443, - SecretName: "eventing-webhook-certs", - WebhookName: "webhook.eventing.knative.dev", + ServiceName: "eventing-webhook", + DeploymentName: "eventing-webhook", + Namespace: system.Namespace, + Port: 443, + SecretName: "eventing-webhook-certs", + WebhookName: "eventing-webhook.eventing.knative.dev", + } + controller := webhook.AdmissionController{ + Client: kubeClient, + Options: options, + // TODO(mattmoor): Will we need to rework these to support versioning? + GroupVersion: v1alpha1.SchemeGroupVersion, + Handlers: map[string]runtime.Object{ + "Revision": &v1alpha1.Revision{}, + "Configuration": &v1alpha1.Configuration{}, + "Route": &v1alpha1.Route{}, + "Service": &v1alpha1.Service{}, + }, + Logger: logger, } - controller, err := webhook.NewAdmissionController(clientset, options) if err != nil { - glog.Fatal("Failed to create the admission controller", err) + logger.Fatal("Failed to create the admission controller", zap.Error(err)) } controller.Run(stopCh) } diff --git a/pkg/logging/OWNERS b/pkg/logging/OWNERS new file mode 100644 index 00000000000..f51b91be775 --- /dev/null +++ b/pkg/logging/OWNERS @@ -0,0 +1,6 @@ +# The OWNERS file is used by prow to automatically merge approved PRs. + +approvers: +- mdemirhan +- n3wscott +- yanweiguo diff --git a/pkg/logging/config.go b/pkg/logging/config.go new file mode 100644 index 00000000000..ae8ad0401c5 --- /dev/null +++ b/pkg/logging/config.go @@ -0,0 +1,61 @@ +/* +Copyright 2018 The Knative 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 logging + +import ( + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + + "github.com/knative/pkg/logging" +) + +const ( + ConfigName = "config-logging" +) + +var components = []string{"controller", "queueproxy", "webhook", "activator", "autoscaler"} + +// NewLogger creates a logger with the supplied configuration. +// In addition to the logger, it returns AtomicLevel that can +// be used to change the logging level at runtime. +// If configuration is empty, a fallback configuration is used. +// If configuration cannot be used to instantiate a logger, +// the same fallback configuration is used. +func NewLogger(configJSON string, levelOverride string) (*zap.SugaredLogger, zap.AtomicLevel) { + return logging.NewLogger(configJSON, levelOverride) +} + +// NewLoggerFromConfig creates a logger using the provided Config +func NewLoggerFromConfig(config *logging.Config, name string) (*zap.SugaredLogger, zap.AtomicLevel) { + return logging.NewLoggerFromConfig(config, name) +} + +// NewConfigFromMap creates a LoggingConfig from the supplied map +func NewConfigFromMap(data map[string]string) (*logging.Config, error) { + return logging.NewConfigFromMap(data, components...) +} + +// NewConfigFromConfigMap creates a LoggingConfig from the supplied ConfigMap +func NewConfigFromConfigMap(configMap *corev1.ConfigMap) (*logging.Config, error) { + return logging.NewConfigFromConfigMap(configMap, components...) +} + +// UpdateLevelFromConfigMap returns a helper func that can be used to update the logging level +// when a config map is updated +func UpdateLevelFromConfigMap(logger *zap.SugaredLogger, atomicLevel zap.AtomicLevel, levelKey string) func(configMap *corev1.ConfigMap) { + return logging.UpdateLevelFromConfigMap(logger, atomicLevel, levelKey, components...) +} diff --git a/pkg/logging/config_test.go b/pkg/logging/config_test.go new file mode 100644 index 00000000000..9f8c21a8664 --- /dev/null +++ b/pkg/logging/config_test.go @@ -0,0 +1,276 @@ +/* +Copyright 2018 The Knative 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 logging + +import ( + "fmt" + "io/ioutil" + "testing" + + "github.com/ghodss/yaml" + "github.com/knative/pkg/logging" + "github.com/knative/serving/pkg/system" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestNewLogger(t *testing.T) { + logger, _ := NewLogger("", "") + if logger == nil { + t.Error("expected a non-nil logger") + } + + logger, _ = NewLogger("some invalid JSON here", "") + if logger == nil { + t.Error("expected a non-nil logger") + } + + logger, atomicLevel := NewLogger("", "debug") + if logger == nil { + t.Error("expected a non-nil logger") + } + if atomicLevel.Level() != zapcore.DebugLevel { + t.Error("expected level to be debug") + } + + // No good way to test if all the config is applied, + // but at the minimum, we can check and see if level is getting applied. + logger, atomicLevel = NewLogger("{\"level\": \"error\", \"outputPaths\": [\"stdout\"],\"errorOutputPaths\": [\"stderr\"],\"encoding\": \"json\"}", "") + if logger == nil { + t.Error("expected a non-nil logger") + } + if ce := logger.Desugar().Check(zap.InfoLevel, "test"); ce != nil { + t.Error("not expected to get info logs from the logger configured with error as min threshold") + } + if ce := logger.Desugar().Check(zap.ErrorLevel, "test"); ce == nil { + t.Error("expected to get error logs from the logger configured with error as min threshold") + } + if atomicLevel.Level() != zapcore.ErrorLevel { + t.Errorf("expected atomicLevel.Level() to be ErrorLevel but got %v.", atomicLevel.Level()) + } + + logger, atomicLevel = NewLogger("{\"level\": \"info\", \"outputPaths\": [\"stdout\"],\"errorOutputPaths\": [\"stderr\"],\"encoding\": \"json\"}", "") + if logger == nil { + t.Error("expected a non-nil logger") + } + if ce := logger.Desugar().Check(zap.DebugLevel, "test"); ce != nil { + t.Error("not expected to get debug logs from the logger configured with info as min threshold") + } + if ce := logger.Desugar().Check(zap.InfoLevel, "test"); ce == nil { + t.Error("expected to get info logs from the logger configured with info as min threshold") + } + if atomicLevel.Level() != zapcore.InfoLevel { + t.Errorf("expected atomicLevel.Level() to be InfoLevel but got %v.", atomicLevel.Level()) + } + + // Let's change the logging level using atomicLevel + atomicLevel.SetLevel(zapcore.ErrorLevel) + if ce := logger.Desugar().Check(zap.InfoLevel, "test"); ce != nil { + t.Error("not expected to get info logs from the logger configured with error as min threshold") + } + if ce := logger.Desugar().Check(zap.ErrorLevel, "test"); ce == nil { + t.Error("expected to get error logs from the logger configured with error as min threshold") + } + if atomicLevel.Level() != zapcore.ErrorLevel { + t.Errorf("expected atomicLevel.Level() to be ErrorLevel but got %v.", atomicLevel.Level()) + } + + // Test logging override + logger, _ = NewLogger("{\"level\": \"error\", \"outputPaths\": [\"stdout\"],\"errorOutputPaths\": [\"stderr\"],\"encoding\": \"json\"}", "info") + if logger == nil { + t.Error("expected a non-nil logger") + } + if ce := logger.Desugar().Check(zap.DebugLevel, "test"); ce != nil { + t.Error("not expected to get debug logs from the logger configured with info as min threshold") + } + if ce := logger.Desugar().Check(zap.InfoLevel, "test"); ce == nil { + t.Error("expected to get info logs from the logger configured with info as min threshold") + } + + // Invalid logging override + logger, _ = NewLogger("{\"level\": \"error\", \"outputPaths\": [\"stdout\"],\"errorOutputPaths\": [\"stderr\"],\"encoding\": \"json\"}", "randomstring") + if logger == nil { + t.Error("expected a non-nil logger") + } + if ce := logger.Desugar().Check(zap.InfoLevel, "test"); ce != nil { + t.Error("not expected to get info logs from the logger configured with error as min threshold") + } + if ce := logger.Desugar().Check(zap.ErrorLevel, "test"); ce == nil { + t.Error("expected to get error logs from the logger configured with error as min threshold") + } +} + +func TestNewConfigNoEntry(t *testing.T) { + c, err := NewConfigFromConfigMap(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: system.Namespace, + Name: "config-logging", + }, + }) + if err != nil { + t.Errorf("Expected no errors. got: %v", err) + } + if got, want := c.LoggingConfig, ""; got != want { + t.Errorf("LoggingConfig = %v, want %v", got, want) + } + if got, want := len(c.LoggingLevel), 0; got != want { + t.Errorf("len(LoggingLevel) = %v, want %v", got, want) + } +} + +func TestNewConfig(t *testing.T) { + wantCfg := "{\"level\": \"error\",\n\"outputPaths\": [\"stdout\"],\n\"errorOutputPaths\": [\"stderr\"],\n\"encoding\": \"json\"}" + wantLevel := zapcore.InfoLevel + c, err := NewConfigFromConfigMap(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: system.Namespace, + Name: "config-logging", + }, + Data: map[string]string{ + "zap-logger-config": wantCfg, + "loglevel.queueproxy": wantLevel.String(), + }, + }) + if err != nil { + t.Errorf("Expected no errors. got: %v", err) + } + if got := c.LoggingConfig; got != wantCfg { + t.Errorf("LoggingConfig = %v, want %v", got, wantCfg) + } + if got := c.LoggingLevel["queueproxy"]; got != wantLevel { + t.Errorf("LoggingLevel[queueproxy] = %v, want %v", got, wantLevel) + } +} + +func TestOurConfig(t *testing.T) { + b, err := ioutil.ReadFile(fmt.Sprintf("testdata/%s.yaml", ConfigName)) + if err != nil { + t.Errorf("ReadFile() = %v", err) + } + var cm corev1.ConfigMap + if err := yaml.Unmarshal(b, &cm); err != nil { + t.Errorf("yaml.Unmarshal() = %v", err) + } + cfg, err := NewConfigFromConfigMap(&cm) + if err != nil { + t.Errorf("Expected no errors. got: %v", err) + } + if cfg == nil { + t.Errorf("NewConfigFromConfigMap() = %v, want non-nil", cfg) + } +} + +func TestNewLoggerFromConfig(t *testing.T) { + c, _, _ := getTestConfig() + _, atomicLevel := NewLoggerFromConfig(c, "queueproxy") + if atomicLevel.Level() != zapcore.DebugLevel { + t.Errorf("logger level wanted: DebugLevel, got: %v", atomicLevel) + } +} + +func TestEmptyLevel(t *testing.T) { + c, err := NewConfigFromConfigMap(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: system.Namespace, + Name: "config-logging", + }, + Data: map[string]string{ + "zap-logger-config": "{\"level\": \"error\",\n\"outputPaths\": [\"stdout\"],\n\"errorOutputPaths\": [\"stderr\"],\n\"encoding\": \"json\"}", + "loglevel.queueproxy": "", + }, + }) + if err != nil { + t.Errorf("Expected no errors. got: %v", err) + } + if _, ok := c.LoggingLevel["queueproxy"]; ok { + t.Errorf("Expected nothing for LoggingLevel[queueproxy]. got: %v", c.LoggingLevel["queueproxy"]) + } +} + +func TestInvalidLevel(t *testing.T) { + wantCfg := "{\"level\": \"error\",\n\"outputPaths\": [\"stdout\"],\n\"errorOutputPaths\": [\"stderr\"],\n\"encoding\": \"json\"}" + _, err := NewConfigFromConfigMap(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: system.Namespace, + Name: "config-logging", + }, + Data: map[string]string{ + "zap-logger-config": wantCfg, + "loglevel.queueproxy": "invalid", + }, + }) + if err == nil { + t.Errorf("Expected errors. got nothing") + } +} + +func getTestConfig() (*logging.Config, string, string) { + wantCfg := "{\"level\": \"error\",\n\"outputPaths\": [\"stdout\"],\n\"errorOutputPaths\": [\"stderr\"],\n\"encoding\": \"json\"}" + wantLevel := "debug" + c, _ := NewConfigFromConfigMap(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: system.Namespace, + Name: "config-logging", + }, + Data: map[string]string{ + "zap-logger-config": wantCfg, + "loglevel.queueproxy": wantLevel, + }, + }) + return c, wantCfg, wantLevel +} + +func TestUpdateLevelFromConfigMap(t *testing.T) { + logger, atomicLevel := NewLogger("", "debug") + want := zapcore.DebugLevel + if atomicLevel.Level() != zapcore.DebugLevel { + t.Fatalf("Expected initial logger level to %v, got: %v", want, atomicLevel.Level()) + } + + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: system.Namespace, + Name: "config-logging", + }, + Data: map[string]string{ + "zap-logger-config": "", + "loglevel.controller": "panic", + }, + } + + tests := []struct { + setLevel string + wantLevel zapcore.Level + }{ + {"info", zapcore.InfoLevel}, + {"error", zapcore.ErrorLevel}, + {"invalid", zapcore.ErrorLevel}, + {"debug", zapcore.DebugLevel}, + {"debug", zapcore.DebugLevel}, + } + + u := UpdateLevelFromConfigMap(logger, atomicLevel, "controller") + for _, tt := range tests { + cm.Data["loglevel.controller"] = tt.setLevel + u(cm) + if atomicLevel.Level() != tt.wantLevel { + t.Errorf("Invalid logging level. want: %v, got: %v", tt.wantLevel, atomicLevel.Level()) + } + } +} diff --git a/pkg/logging/logger.go b/pkg/logging/logger.go new file mode 100644 index 00000000000..843306c37c5 --- /dev/null +++ b/pkg/logging/logger.go @@ -0,0 +1,38 @@ +/* +Copyright 2018 The Knative 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 logging + +import ( + "context" + + "go.uber.org/zap" + + "github.com/knative/pkg/logging" +) + +// WithLogger returns a copy of parent context in which the +// value associated with logger key is the supplied logger. +func WithLogger(ctx context.Context, logger *zap.SugaredLogger) context.Context { + return logging.WithLogger(ctx, logger) +} + +// FromContext returns the logger stored in context. +// Returns nil if no logger is set in context, or if the stored value is +// not of correct type. +func FromContext(ctx context.Context) *zap.SugaredLogger { + return logging.FromContext(ctx) +} diff --git a/pkg/logging/logkey/constants.go b/pkg/logging/logkey/constants.go new file mode 100644 index 00000000000..70373dec41f --- /dev/null +++ b/pkg/logging/logkey/constants.go @@ -0,0 +1,34 @@ +/* +Copyright 2018 The Knative 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 logkey + +const ( + // Service is the key used for service name in structured logs + Service = "knative.dev/service" + + // Configuration is the key used for configuration name in structured logs + Configuration = "knative.dev/configuration" + + // Revision is the key used for revision name in structured logs + Revision = "knative.dev/revision" + + // Route is the key used for route name in structured logs + Route = "knative.dev/route" + + // Build is the key used for build name in structured logs + Build = "knative.dev/build" +) diff --git a/pkg/logging/testdata/config-logging.yaml b/pkg/logging/testdata/config-logging.yaml new file mode 100644 index 00000000000..de338a67c68 --- /dev/null +++ b/pkg/logging/testdata/config-logging.yaml @@ -0,0 +1,52 @@ +# Copyright 2018 The Knative 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 +# +# https://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. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-logging + namespace: knative-serving +data: + # Common configuration for all Knative codebase + zap-logger-config: | + { + "level": "info", + "development": false, + "outputPaths": ["stdout"], + "errorOutputPaths": ["stderr"], + "encoding": "json", + "encoderConfig": { + "timeKey": "ts", + "levelKey": "level", + "nameKey": "logger", + "callerKey": "caller", + "messageKey": "msg", + "stacktraceKey": "stacktrace", + "lineEnding": "", + "levelEncoder": "", + "timeEncoder": "iso8601", + "durationEncoder": "", + "callerEncoder": "" + } + } + + # Log level overrides + # For all components except the autoscaler and queue proxy, + # changes are be picked up immediately. + # For autoscaler and queue proxy, changes require recreation of the pods. + loglevel.controller: "info" + loglevel.autoscaler: "info" + loglevel.queueproxy: "info" + loglevel.webhook: "info" + loglevel.activator: "info" From 9905fe67e0f2b1bc5dcea24624cfc371eeb78987 Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Wed, 1 Aug 2018 11:08:00 -0700 Subject: [PATCH 02/13] Adding logging config for eventing to be used with knative/pkg/logging. --- Gopkg.lock | 23 + pkg/logging/config.go | 2 +- pkg/logging/config_test.go | 20 +- pkg/logging/logkey/constants.go | 34 +- pkg/logging/testdata/config-logging.yaml | 7 +- vendor/github.com/knative/pkg/LICENSE | 201 ++++++ .../github.com/knative/pkg/apis/defaults.go | 23 + .../knative/pkg/apis/field_error.go | 110 +++ .../knative/pkg/configmap/default.go | 128 ++++ .../github.com/knative/pkg/configmap/doc.go | 21 + .../github.com/knative/pkg/configmap/fixed.go | 55 ++ .../github.com/knative/pkg/configmap/load.go | 58 ++ .../knative/pkg/configmap/watcher.go | 54 ++ .../github.com/knative/pkg/logging/config.go | 150 ++++ .../github.com/knative/pkg/logging/logger.go | 57 ++ .../knative/pkg/logging/logkey/constants.go | 55 ++ .../pkg/logging/zz_generated.deepcopy.go | 48 ++ .../github.com/knative/pkg/signals/signal.go | 43 ++ .../knative/pkg/signals/signal_posix.go | 26 + .../knative/pkg/signals/signal_windows.go | 23 + .../github.com/knative/pkg/webhook/certs.go | 161 +++++ .../github.com/knative/pkg/webhook/webhook.go | 646 ++++++++++++++++++ 22 files changed, 1919 insertions(+), 26 deletions(-) create mode 100644 vendor/github.com/knative/pkg/LICENSE create mode 100644 vendor/github.com/knative/pkg/apis/defaults.go create mode 100644 vendor/github.com/knative/pkg/apis/field_error.go create mode 100644 vendor/github.com/knative/pkg/configmap/default.go create mode 100644 vendor/github.com/knative/pkg/configmap/doc.go create mode 100644 vendor/github.com/knative/pkg/configmap/fixed.go create mode 100644 vendor/github.com/knative/pkg/configmap/load.go create mode 100644 vendor/github.com/knative/pkg/configmap/watcher.go create mode 100644 vendor/github.com/knative/pkg/logging/config.go create mode 100644 vendor/github.com/knative/pkg/logging/logger.go create mode 100644 vendor/github.com/knative/pkg/logging/logkey/constants.go create mode 100644 vendor/github.com/knative/pkg/logging/zz_generated.deepcopy.go create mode 100644 vendor/github.com/knative/pkg/signals/signal.go create mode 100644 vendor/github.com/knative/pkg/signals/signal_posix.go create mode 100644 vendor/github.com/knative/pkg/signals/signal_windows.go create mode 100644 vendor/github.com/knative/pkg/webhook/certs.go create mode 100644 vendor/github.com/knative/pkg/webhook/webhook.go diff --git a/Gopkg.lock b/Gopkg.lock index 05cfa2c1b6f..a9bd1cb1152 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -260,6 +260,21 @@ pruneopts = "NUT" revision = "5c1d8c8469d1ed34b2aecf4c2305b3a57fff2ee3" +[[projects]] + branch = "master" + digest = "1:d2ad0b894debee75e84ccbb964a26b77fbfbd983e0e2f36242fc3ba028d5c3ac" + name = "github.com/knative/pkg" + packages = [ + "apis", + "configmap", + "logging", + "logging/logkey", + "signals", + "webhook", + ] + pruneopts = "NUT" + revision = "fdf2fdcd93ddadf126cad6991d43a90f20033ceb" + [[projects]] digest = "1:c0361b10d998857122ab480346b688f7dfabf5e9b34e52d786e8e6fd02b602d9" name = "github.com/knative/serving" @@ -1000,12 +1015,18 @@ "github.com/Shopify/sarama", "github.com/bsm/sarama-cluster", "github.com/davecgh/go-spew/spew", + "github.com/ghodss/yaml", "github.com/golang/glog", "github.com/google/go-cmp/cmp", "github.com/google/go-cmp/cmp/cmpopts", "github.com/google/go-github/github", "github.com/google/uuid", "github.com/knative/build/pkg/apis/build/v1alpha1", + "github.com/knative/pkg/configmap", + "github.com/knative/pkg/logging", + "github.com/knative/pkg/logging/logkey", + "github.com/knative/pkg/signals", + "github.com/knative/pkg/webhook", "github.com/knative/serving/pkg/apis/istio/v1alpha3", "github.com/knative/serving/pkg/apis/serving/v1alpha1", "github.com/knative/serving/pkg/client/clientset/versioned", @@ -1013,6 +1034,8 @@ "github.com/knative/serving/pkg/client/listers/istio/v1alpha3", "github.com/mattbaird/jsonpatch", "github.com/prometheus/client_golang/prometheus/promhttp", + "go.uber.org/zap", + "go.uber.org/zap/zapcore", "golang.org/x/net/context", "golang.org/x/oauth2", "google.golang.org/grpc/codes", diff --git a/pkg/logging/config.go b/pkg/logging/config.go index ae8ad0401c5..8cf70b538e4 100644 --- a/pkg/logging/config.go +++ b/pkg/logging/config.go @@ -27,7 +27,7 @@ const ( ConfigName = "config-logging" ) -var components = []string{"controller", "queueproxy", "webhook", "activator", "autoscaler"} +var components = []string{"controller", "controller-manager", "webhook"} // NewLogger creates a logger with the supplied configuration. // In addition to the logger, it returns AtomicLevel that can diff --git a/pkg/logging/config_test.go b/pkg/logging/config_test.go index 9f8c21a8664..01f6ef239a2 100644 --- a/pkg/logging/config_test.go +++ b/pkg/logging/config_test.go @@ -22,8 +22,8 @@ import ( "testing" "github.com/ghodss/yaml" + "github.com/knative/eventing/pkg/system" "github.com/knative/pkg/logging" - "github.com/knative/serving/pkg/system" "go.uber.org/zap" "go.uber.org/zap/zapcore" corev1 "k8s.io/api/core/v1" @@ -144,7 +144,7 @@ func TestNewConfig(t *testing.T) { }, Data: map[string]string{ "zap-logger-config": wantCfg, - "loglevel.queueproxy": wantLevel.String(), + "loglevel.controller": wantLevel.String(), }, }) if err != nil { @@ -153,8 +153,8 @@ func TestNewConfig(t *testing.T) { if got := c.LoggingConfig; got != wantCfg { t.Errorf("LoggingConfig = %v, want %v", got, wantCfg) } - if got := c.LoggingLevel["queueproxy"]; got != wantLevel { - t.Errorf("LoggingLevel[queueproxy] = %v, want %v", got, wantLevel) + if got := c.LoggingLevel["controller"]; got != wantLevel { + t.Errorf("LoggingLevel[controller] = %v, want %v", got, wantLevel) } } @@ -178,7 +178,7 @@ func TestOurConfig(t *testing.T) { func TestNewLoggerFromConfig(t *testing.T) { c, _, _ := getTestConfig() - _, atomicLevel := NewLoggerFromConfig(c, "queueproxy") + _, atomicLevel := NewLoggerFromConfig(c, "controller") if atomicLevel.Level() != zapcore.DebugLevel { t.Errorf("logger level wanted: DebugLevel, got: %v", atomicLevel) } @@ -192,14 +192,14 @@ func TestEmptyLevel(t *testing.T) { }, Data: map[string]string{ "zap-logger-config": "{\"level\": \"error\",\n\"outputPaths\": [\"stdout\"],\n\"errorOutputPaths\": [\"stderr\"],\n\"encoding\": \"json\"}", - "loglevel.queueproxy": "", + "loglevel.controller": "", }, }) if err != nil { t.Errorf("Expected no errors. got: %v", err) } - if _, ok := c.LoggingLevel["queueproxy"]; ok { - t.Errorf("Expected nothing for LoggingLevel[queueproxy]. got: %v", c.LoggingLevel["queueproxy"]) + if _, ok := c.LoggingLevel["controller"]; ok { + t.Errorf("Expected nothing for LoggingLevel[controller]. got: %v", c.LoggingLevel["controller"]) } } @@ -212,7 +212,7 @@ func TestInvalidLevel(t *testing.T) { }, Data: map[string]string{ "zap-logger-config": wantCfg, - "loglevel.queueproxy": "invalid", + "loglevel.controller": "invalid", }, }) if err == nil { @@ -230,7 +230,7 @@ func getTestConfig() (*logging.Config, string, string) { }, Data: map[string]string{ "zap-logger-config": wantCfg, - "loglevel.queueproxy": wantLevel, + "loglevel.controller": wantLevel, }, }) return c, wantCfg, wantLevel diff --git a/pkg/logging/logkey/constants.go b/pkg/logging/logkey/constants.go index 70373dec41f..27e1f7a273f 100644 --- a/pkg/logging/logkey/constants.go +++ b/pkg/logging/logkey/constants.go @@ -17,18 +17,32 @@ limitations under the License. package logkey const ( - // Service is the key used for service name in structured logs - Service = "knative.dev/service" + kNative = "knative.dev/" - // Configuration is the key used for configuration name in structured logs - Configuration = "knative.dev/configuration" + // ClusterEventSource is the key used for cluster scoped event source name in structured logs + ClusterEventSource = kNative + "clustereventsource" - // Revision is the key used for revision name in structured logs - Revision = "knative.dev/revision" + // ClusterEventType is the key used for cluster event type name in structured logs + ClusterEventType = kNative + "clustereventtype" - // Route is the key used for route name in structured logs - Route = "knative.dev/route" + // EventSource is the key used for event source name in structured logs + EventSource = kNative + "eventsource" - // Build is the key used for build name in structured logs - Build = "knative.dev/build" + // EventType is the key used for event type name in structured logs + EventType = kNative + "eventtype" + + // Feed is the key used for feed name in structured logs + Feed = kNative + "feed" + + // Flow is the key used for flow name in structured logs + Flow = kNative + "flow" + + // ClusterBus is the key used for cluster scoped bus name in structured logs + ClusterBus = kNative + "clusterbus" + + // Bus is the key used for bus name in structured logs + Bus = kNative + "bus" + + // Channel is the key used for channel name in structured logs + Channel = kNative + "channel" ) diff --git a/pkg/logging/testdata/config-logging.yaml b/pkg/logging/testdata/config-logging.yaml index de338a67c68..895a0e7ed6b 100644 --- a/pkg/logging/testdata/config-logging.yaml +++ b/pkg/logging/testdata/config-logging.yaml @@ -16,7 +16,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: config-logging - namespace: knative-serving + namespace: knative-eventing data: # Common configuration for all Knative codebase zap-logger-config: | @@ -44,9 +44,6 @@ data: # Log level overrides # For all components except the autoscaler and queue proxy, # changes are be picked up immediately. - # For autoscaler and queue proxy, changes require recreation of the pods. loglevel.controller: "info" - loglevel.autoscaler: "info" - loglevel.queueproxy: "info" + loglevel.controller-manager: "info" loglevel.webhook: "info" - loglevel.activator: "info" diff --git a/vendor/github.com/knative/pkg/LICENSE b/vendor/github.com/knative/pkg/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/vendor/github.com/knative/pkg/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/knative/pkg/apis/defaults.go b/vendor/github.com/knative/pkg/apis/defaults.go new file mode 100644 index 00000000000..20893f8e510 --- /dev/null +++ b/vendor/github.com/knative/pkg/apis/defaults.go @@ -0,0 +1,23 @@ +/* +Copyright 2017 The Knative 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 apis + +// Defaultable defines an interface for setting the defaults for the +// uninitialized fields of this instance. +type Defaultable interface { + SetDefaults() +} diff --git a/vendor/github.com/knative/pkg/apis/field_error.go b/vendor/github.com/knative/pkg/apis/field_error.go new file mode 100644 index 00000000000..079041a251e --- /dev/null +++ b/vendor/github.com/knative/pkg/apis/field_error.go @@ -0,0 +1,110 @@ +/* +Copyright 2017 The Knative 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 apis + +import ( + "fmt" + "strings" +) + +// CurrentField is a constant to supply as a fieldPath for when there is +// a problem with the current field itself. +const CurrentField = "" + +// FieldError is used to propagate the context of errors pertaining to +// specific fields in a manner suitable for use in a recursive walk, so +// that errors contain the appropriate field context. +// +k8s:deepcopy-gen=false +type FieldError struct { + Message string + Paths []string + // Details contains an optional longer payload. + Details string +} + +// FieldError implements error +var _ error = (*FieldError)(nil) + +// Validatable indicates that a particular type may have its fields validated. +type Validatable interface { + // Validate checks the validity of this types fields. + Validate() *FieldError +} + +// Immutable indicates that a particular type has fields that should +// not change after creation. +type Immutable interface { + // CheckImmutableFields checks that the current instance's immutable + // fields haven't changed from the provided original. + CheckImmutableFields(original Immutable) *FieldError +} + +// ViaField is used to propagate a validation error along a field access. +// For example, if a type recursively validates its "spec" via: +// if err := foo.Spec.Validate(); err != nil { +// // Augment any field paths with the context that they were accessed +// // via "spec". +// return err.ViaField("spec") +// } +func (fe *FieldError) ViaField(prefix ...string) *FieldError { + if fe == nil { + return nil + } + var newPaths []string + for _, oldPath := range fe.Paths { + if oldPath == CurrentField { + newPaths = append(newPaths, strings.Join(prefix, ".")) + } else { + newPaths = append(newPaths, + strings.Join(append(prefix, oldPath), ".")) + } + } + fe.Paths = newPaths + return fe +} + +// Error implements error +func (fe *FieldError) Error() string { + if fe.Details == "" { + return fmt.Sprintf("%v: %v", fe.Message, strings.Join(fe.Paths, ", ")) + } + return fmt.Sprintf("%v: %v\n%v", fe.Message, strings.Join(fe.Paths, ", "), fe.Details) +} + +// ErrMissingField is a variadic helper method for constructing a FieldError for a set of missing fields. +func ErrMissingField(fieldPaths ...string) *FieldError { + return &FieldError{ + Message: "missing field(s)", + Paths: fieldPaths, + } +} + +// ErrDisallowedFields is a variadic helper method for constructing a FieldError for a set of disallowed fields. +func ErrDisallowedFields(fieldPaths ...string) *FieldError { + return &FieldError{ + Message: "must not set the field(s)", + Paths: fieldPaths, + } +} + +// ErrInvalidValue constructs a FieldError for a field that has received an invalid string value. +func ErrInvalidValue(value string, fieldPath string) *FieldError { + return &FieldError{ + Message: fmt.Sprintf("invalid value %q", value), + Paths: []string{fieldPath}, + } +} diff --git a/vendor/github.com/knative/pkg/configmap/default.go b/vendor/github.com/knative/pkg/configmap/default.go new file mode 100644 index 00000000000..0f30cf6d928 --- /dev/null +++ b/vendor/github.com/knative/pkg/configmap/default.go @@ -0,0 +1,128 @@ +/* +Copyright 2018 The Knative 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 configmap + +import ( + "errors" + "sync" + + corev1 "k8s.io/api/core/v1" + informers "k8s.io/client-go/informers" + corev1informers "k8s.io/client-go/informers/core/v1" + "k8s.io/client-go/tools/cache" +) + +// defaultImpl provides a default informer-based implementation of Watcher. +type defaultImpl struct { + sif informers.SharedInformerFactory + informer corev1informers.ConfigMapInformer + ns string + + // Guards mutations to defaultImpl fields + m sync.Mutex + + observers map[string][]Observer + started bool +} + +// Asserts that defaultImpl implements Watcher. +var _ Watcher = (*defaultImpl)(nil) + +// Watch implements Watcher +func (di *defaultImpl) Watch(name string, w Observer) { + di.m.Lock() + defer di.m.Unlock() + + if di.observers == nil { + di.observers = make(map[string][]Observer) + } + + wl, _ := di.observers[name] + di.observers[name] = append(wl, w) +} + +// Start implements Watcher +func (di *defaultImpl) Start(stopCh <-chan struct{}) error { + if err := di.registerCallbackAndStartInformer(stopCh); err != nil { + return err + } + + // Wait until it has been synced (WITHOUT holding the mutex, so callbacks happen) + if ok := cache.WaitForCacheSync(stopCh, di.informer.Informer().HasSynced); !ok { + return errors.New("Error waiting for ConfigMap informer to sync.") + } + + return di.checkObservedResourcesExist() +} + +func (di *defaultImpl) registerCallbackAndStartInformer(stopCh <-chan struct{}) error { + di.m.Lock() + defer di.m.Unlock() + if di.started { + return errors.New("Watcher already started!") + } + di.started = true + + di.informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: di.addConfigMapEvent, + UpdateFunc: di.updateConfigMapEvent, + }) + + // Start the shared informer factory (non-blocking) + di.sif.Start(stopCh) + return nil +} + +func (di *defaultImpl) checkObservedResourcesExist() error { + di.m.Lock() + defer di.m.Unlock() + // Check that all objects with Observers exist in our informers. + for k := range di.observers { + _, err := di.informer.Lister().ConfigMaps(di.ns).Get(k) + if err != nil { + return err + } + } + return nil +} + +func (di *defaultImpl) addConfigMapEvent(obj interface{}) { + // If the ConfigMap update is outside of our namespace, then quickly disregard it. + configMap := obj.(*corev1.ConfigMap) + if configMap.Namespace != di.ns { + // Outside of our namespace. + // This shouldn't happen due to our filtered informer. + return + } + + // Within our namespace, take the lock and see if there are any registered observers. + di.m.Lock() + defer di.m.Unlock() + wl, ok := di.observers[configMap.Name] + if !ok { + return // No observers. + } + + // Iterate over the observers and invoke their callbacks. + for _, w := range wl { + w(configMap) + } +} + +func (di *defaultImpl) updateConfigMapEvent(old, new interface{}) { + di.addConfigMapEvent(new) +} diff --git a/vendor/github.com/knative/pkg/configmap/doc.go b/vendor/github.com/knative/pkg/configmap/doc.go new file mode 100644 index 00000000000..d861a3801f5 --- /dev/null +++ b/vendor/github.com/knative/pkg/configmap/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2018 The Knative 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 configmap exists to facilitate consuming Kubernetes ConfigMap +// resources in various ways, including: +// - Watching them for changes over time, and +// - Loading them from a VolumeMount. +package configmap diff --git a/vendor/github.com/knative/pkg/configmap/fixed.go b/vendor/github.com/knative/pkg/configmap/fixed.go new file mode 100644 index 00000000000..d915e7ae084 --- /dev/null +++ b/vendor/github.com/knative/pkg/configmap/fixed.go @@ -0,0 +1,55 @@ +/* +Copyright 2018 The Knative 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 configmap + +import ( + "log" + + corev1 "k8s.io/api/core/v1" +) + +// fixedImpl provides a fixed informer-based implementation of Watcher. +type fixedImpl struct { + cfgs map[string]*corev1.ConfigMap +} + +// Asserts that fixedImpl implements Watcher. +var _ Watcher = (*fixedImpl)(nil) + +// Watch implements Watcher +func (di *fixedImpl) Watch(name string, w Observer) { + cm, ok := di.cfgs[name] + if ok { + w(cm) + } else { + log.Printf("Name %q is not found.", name) + } +} + +// Start implements Watcher +func (di *fixedImpl) Start(stopCh <-chan struct{}) error { + return nil +} + +// NewFixedWatcher returns an Watcher that exposes the fixed collection of ConfigMaps. +func NewFixedWatcher(cms ...*corev1.ConfigMap) Watcher { + cmm := make(map[string]*corev1.ConfigMap) + for _, cm := range cms { + cmm[cm.Name] = cm + } + return &fixedImpl{cfgs: cmm} +} diff --git a/vendor/github.com/knative/pkg/configmap/load.go b/vendor/github.com/knative/pkg/configmap/load.go new file mode 100644 index 00000000000..ee40fbf0cc0 --- /dev/null +++ b/vendor/github.com/knative/pkg/configmap/load.go @@ -0,0 +1,58 @@ +/* +Copyright 2018 The Knative 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 configmap + +import ( + "io/ioutil" + "os" + "path" + "path/filepath" +) + +// Load reads the "Data" of a ConfigMap from a particular VolumeMount. +func Load(p string) (map[string]string, error) { + data := make(map[string]string) + err := filepath.Walk(p, func(p string, info os.FileInfo, err error) error { + if err != nil { + return err + } + for info.Mode()&os.ModeSymlink != 0 { + dirname := filepath.Dir(p) + p, err = os.Readlink(p) + if err != nil { + return err + } + if !filepath.IsAbs(p) { + p = path.Join(dirname, p) + } + info, err = os.Lstat(p) + if err != nil { + return err + } + } + if info.IsDir() { + return nil + } + b, err := ioutil.ReadFile(p) + if err != nil { + return err + } + data[info.Name()] = string(b) + return nil + }) + return data, err +} diff --git a/vendor/github.com/knative/pkg/configmap/watcher.go b/vendor/github.com/knative/pkg/configmap/watcher.go new file mode 100644 index 00000000000..ccb75f36ceb --- /dev/null +++ b/vendor/github.com/knative/pkg/configmap/watcher.go @@ -0,0 +1,54 @@ +/* +Copyright 2018 The Knative 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 configmap + +import ( + "time" + + corev1 "k8s.io/api/core/v1" + kubeinformers "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" +) + +// Observer is the signature of the callbacks that notify an observer of the latest +// state of a particular configuration. An observer should not modify the provided +// ConfigMap, and should `.DeepCopy()` it for persistence (or otherwise process its +// contents). +type Observer func(*corev1.ConfigMap) + +// Watcher defined the interface that a configmap implementation must implement. +type Watcher interface { + // Watch is called to register a callback to be notified when a named ConfigMap changes. + Watch(string, Observer) + + // Start is called to initiate the watches and provide a channel to signal when we should + // stop watching. When Start returns, all registered Observers will be called with the + // initial state of the ConfigMaps they are watching. + Start(<-chan struct{}) error +} + +// NewDefaultWatcher creates a new default configmap.Watcher instance. +func NewDefaultWatcher(kc kubernetes.Interface, ns string) Watcher { + sif := kubeinformers.NewFilteredSharedInformerFactory( + kc, 5*time.Minute, ns, nil) + + return &defaultImpl{ + sif: sif, + informer: sif.Core().V1().ConfigMaps(), + ns: ns, + } +} diff --git a/vendor/github.com/knative/pkg/logging/config.go b/vendor/github.com/knative/pkg/logging/config.go new file mode 100644 index 00000000000..e0cc74abaae --- /dev/null +++ b/vendor/github.com/knative/pkg/logging/config.go @@ -0,0 +1,150 @@ +/* +Copyright 2018 The Knative 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 logging + +import ( + "encoding/json" + "errors" + "fmt" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + corev1 "k8s.io/api/core/v1" + + "github.com/knative/pkg/logging/logkey" +) + +// NewLogger creates a logger with the supplied configuration. +// In addition to the logger, it returns AtomicLevel that can +// be used to change the logging level at runtime. +// If configuration is empty, a fallback configuration is used. +// If configuration cannot be used to instantiate a logger, +// the same fallback configuration is used. +func NewLogger(configJSON string, levelOverride string) (*zap.SugaredLogger, zap.AtomicLevel) { + logger, atomicLevel, err := newLoggerFromConfig(configJSON, levelOverride) + if err == nil { + return logger.Sugar(), atomicLevel + } + + loggingCfg := zap.NewProductionConfig() + if len(levelOverride) > 0 { + if level, err := levelFromString(levelOverride); err == nil { + loggingCfg.Level = zap.NewAtomicLevelAt(*level) + } + } + + logger, err2 := loggingCfg.Build() + if err2 != nil { + panic(err2) + } + return logger.Named("fallback-logger").Sugar(), loggingCfg.Level +} + +// NewLoggerFromConfig creates a logger using the provided Config +func NewLoggerFromConfig(config *Config, name string) (*zap.SugaredLogger, zap.AtomicLevel) { + logger, level := NewLogger(config.LoggingConfig, config.LoggingLevel[name].String()) + return logger.Named(name), level +} + +func newLoggerFromConfig(configJSON string, levelOverride string) (*zap.Logger, zap.AtomicLevel, error) { + if len(configJSON) == 0 { + return nil, zap.AtomicLevel{}, errors.New("empty logging configuration") + } + + var loggingCfg zap.Config + if err := json.Unmarshal([]byte(configJSON), &loggingCfg); err != nil { + return nil, zap.AtomicLevel{}, err + } + + if len(levelOverride) > 0 { + if level, err := levelFromString(levelOverride); err == nil { + loggingCfg.Level = zap.NewAtomicLevelAt(*level) + } + } + + logger, err := loggingCfg.Build() + if err != nil { + return nil, zap.AtomicLevel{}, err + } + + logger.Info("Successfully created the logger.", zap.String(logkey.JSONConfig, configJSON)) + logger.Sugar().Infof("Logging level set to %v", loggingCfg.Level) + return logger, loggingCfg.Level, nil +} + +// Config contains the configuration defined in the logging ConfigMap. +// +k8s:deepcopy-gen=true +type Config struct { + LoggingConfig string + LoggingLevel map[string]zapcore.Level +} + +// NewConfigFromMap creates a LoggingConfig from the supplied map, +// expecting the given list of components. +func NewConfigFromMap(data map[string]string, components ...string) (*Config, error) { + lc := &Config{} + if zlc, ok := data["zap-logger-config"]; ok { + lc.LoggingConfig = zlc + } + lc.LoggingLevel = make(map[string]zapcore.Level) + for _, component := range components { + if ll, ok := data["loglevel."+component]; ok { + if len(ll) > 0 { + level, err := levelFromString(ll) + if err != nil { + return nil, err + } + lc.LoggingLevel[component] = *level + } + } + } + return lc, nil +} + +// NewConfigFromConfigMap creates a LoggingConfig from the supplied ConfigMap, +// expecting the given list of components. +func NewConfigFromConfigMap(configMap *corev1.ConfigMap, components ...string) (*Config, error) { + return NewConfigFromMap(configMap.Data, components...) +} + +func levelFromString(level string) (*zapcore.Level, error) { + var zapLevel zapcore.Level + if err := zapLevel.UnmarshalText([]byte(level)); err != nil { + return nil, fmt.Errorf("invalid logging level: %v", level) + } + return &zapLevel, nil +} + +// UpdateLevelFromConfigMap returns a helper func that can be used to update the logging level +// when a config map is updated +func UpdateLevelFromConfigMap(logger *zap.SugaredLogger, atomicLevel zap.AtomicLevel, + levelKey string, components ...string) func(configMap *corev1.ConfigMap) { + return func(configMap *corev1.ConfigMap) { + loggingConfig, err := NewConfigFromConfigMap(configMap, components...) + if err != nil { + logger.Error("Failed to parse the logging configmap. Previous config map will be used.", zap.Error(err)) + return + } + + if level, ok := loggingConfig.LoggingLevel[levelKey]; ok { + if atomicLevel.Level() != level { + logger.Infof("Updating logging level for %v from %v to %v.", levelKey, atomicLevel.Level(), level) + atomicLevel.SetLevel(level) + } + } + } +} diff --git a/vendor/github.com/knative/pkg/logging/logger.go b/vendor/github.com/knative/pkg/logging/logger.go new file mode 100644 index 00000000000..903dc5825ef --- /dev/null +++ b/vendor/github.com/knative/pkg/logging/logger.go @@ -0,0 +1,57 @@ +/* +Copyright 2018 The Knative 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 logging + +import ( + "context" + + "go.uber.org/zap" +) + +type loggerKey struct{} + +// This logger is used when there is no logger attached to the context. +// Rather than returning nil and causing a panic, we will use the fallback +// logger. Fallback logger is tagged with logger=fallback to make sure +// that code that doesn't set the logger correctly can be caught at runtime. +var fallbackLogger *zap.SugaredLogger + +func init() { + if logger, err := zap.NewProduction(); err != nil { + // We failed to create a fallback logger. Our fallback + // unfortunately falls back to noop. + fallbackLogger = zap.NewNop().Sugar() + } else { + fallbackLogger = logger.Named("fallback").Sugar() + } +} + +// WithLogger returns a copy of parent context in which the +// value associated with logger key is the supplied logger. +func WithLogger(ctx context.Context, logger *zap.SugaredLogger) context.Context { + return context.WithValue(ctx, loggerKey{}, logger) +} + +// FromContext returns the logger stored in context. +// Returns nil if no logger is set in context, or if the stored value is +// not of correct type. +func FromContext(ctx context.Context) *zap.SugaredLogger { + if logger, ok := ctx.Value(loggerKey{}).(*zap.SugaredLogger); ok { + return logger + } + return fallbackLogger +} diff --git a/vendor/github.com/knative/pkg/logging/logkey/constants.go b/vendor/github.com/knative/pkg/logging/logkey/constants.go new file mode 100644 index 00000000000..d38420e5af7 --- /dev/null +++ b/vendor/github.com/knative/pkg/logging/logkey/constants.go @@ -0,0 +1,55 @@ +/* +Copyright 2018 The Knative 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 logkey + +const ( + // ControllerType is the key used for controller type in structured logs + ControllerType = "knative.dev/controller" + + // Namespace is the key used for namespace in structured logs + Namespace = "knative.dev/namespace" + + // JSONConfig is the key used for JSON configurations (not to be confused by the Configuration object) + JSONConfig = "knative.dev/jsonconfig" + + // Kind is the key used to represent kind of an object in logs + Kind = "knative.dev/kind" + + // Name is the key used to represent name of an object in logs + Name = "knative.dev/name" + + // Operation is the key used to represent an operation in logs + Operation = "knative.dev/operation" + + // Resource is the key used to represent a resource in logs + Resource = "knative.dev/resource" + + // SubResource is a generic key used to represent a sub-resource in logs + SubResource = "knative.dev/subresource" + + // UserInfo is the key used to represent a user information in logs + UserInfo = "knative.dev/userinfo" + + // Pod is the key used to represent a pod's name in logs + Pod = "knative.dev/pod" + + // Deployment is the key used to represent a deployment's name in logs + Deployment = "knative.dev/deployment" + + // KubernetesService is the key used to represent a Kubernetes service name in logs + KubernetesService = "knative.dev/k8sservice" +) diff --git a/vendor/github.com/knative/pkg/logging/zz_generated.deepcopy.go b/vendor/github.com/knative/pkg/logging/zz_generated.deepcopy.go new file mode 100644 index 00000000000..19b3ddc8b43 --- /dev/null +++ b/vendor/github.com/knative/pkg/logging/zz_generated.deepcopy.go @@ -0,0 +1,48 @@ +// +build !ignore_autogenerated + +/* +Copyright 2018 The Knative 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package logging + +import ( + zapcore "go.uber.org/zap/zapcore" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Config) DeepCopyInto(out *Config) { + *out = *in + if in.LoggingLevel != nil { + in, out := &in.LoggingLevel, &out.LoggingLevel + *out = make(map[string]zapcore.Level, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config. +func (in *Config) DeepCopy() *Config { + if in == nil { + return nil + } + out := new(Config) + in.DeepCopyInto(out) + return out +} diff --git a/vendor/github.com/knative/pkg/signals/signal.go b/vendor/github.com/knative/pkg/signals/signal.go new file mode 100644 index 00000000000..bb9d8c3f893 --- /dev/null +++ b/vendor/github.com/knative/pkg/signals/signal.go @@ -0,0 +1,43 @@ +/* +Copyright 2018 The Knative 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 signals + +import ( + "os" + "os/signal" +) + +var onlyOneSignalHandler = make(chan struct{}) + +// SetupSignalHandler registered for SIGTERM and SIGINT. A stop channel is returned +// which is closed on one of these signals. If a second signal is caught, the program +// is terminated with exit code 1. +func SetupSignalHandler() (stopCh <-chan struct{}) { + close(onlyOneSignalHandler) // panics when called twice + + stop := make(chan struct{}) + c := make(chan os.Signal, 2) + signal.Notify(c, shutdownSignals...) + go func() { + <-c + close(stop) + <-c + os.Exit(1) // second signal. Exit directly. + }() + + return stop +} diff --git a/vendor/github.com/knative/pkg/signals/signal_posix.go b/vendor/github.com/knative/pkg/signals/signal_posix.go new file mode 100644 index 00000000000..b3537d0e5dc --- /dev/null +++ b/vendor/github.com/knative/pkg/signals/signal_posix.go @@ -0,0 +1,26 @@ +// +build !windows + +/* +Copyright 2018 The Knative 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 signals + +import ( + "os" + "syscall" +) + +var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM} diff --git a/vendor/github.com/knative/pkg/signals/signal_windows.go b/vendor/github.com/knative/pkg/signals/signal_windows.go new file mode 100644 index 00000000000..a5a4026faad --- /dev/null +++ b/vendor/github.com/knative/pkg/signals/signal_windows.go @@ -0,0 +1,23 @@ +/* +Copyright 2018 The Knative 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 signals + +import ( + "os" +) + +var shutdownSignals = []os.Signal{os.Interrupt} diff --git a/vendor/github.com/knative/pkg/webhook/certs.go b/vendor/github.com/knative/pkg/webhook/certs.go new file mode 100644 index 00000000000..8bc80b4f4ef --- /dev/null +++ b/vendor/github.com/knative/pkg/webhook/certs.go @@ -0,0 +1,161 @@ +/* +Copyright 2018 The Knative 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 webhook + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "errors" + "math/big" + "time" + + "go.uber.org/zap" + + "github.com/knative/pkg/logging" +) + +const ( + // TODO(vaikas): Add / change other parts of the cert we might care about + organization = "kube" +) + +// Create the common parts of the cert. These don't change between +// the root/CA cert and the server cert. +func createCertTemplate(name, namespace string) (*x509.Certificate, error) { + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, errors.New("failed to generate serial number: " + err.Error()) + } + + serviceName := name + "." + namespace + serviceNames := []string{serviceName, serviceName + ".svc", serviceName + ".svc.cluster.local"} + + tmpl := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{Organization: []string{organization}}, + SignatureAlgorithm: x509.SHA256WithRSA, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(1, 0, 0), // valid for 1 years + BasicConstraintsValid: true, + DNSNames: serviceNames, + } + return &tmpl, nil +} + +// Create cert template suitable for CA and hence signing +func createCACertTemplate(name, namespace string) (*x509.Certificate, error) { + rootCert, err := createCertTemplate(name, namespace) + if err != nil { + return nil, err + } + // Make it into a CA cert and change it so we can use it to sign certs + rootCert.IsCA = true + rootCert.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature + rootCert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} + return rootCert, nil +} + +// Create cert template that we can use on the server for TLS +func createServerCertTemplate(name, namespace string) (*x509.Certificate, error) { + serverCert, err := createCertTemplate(name, namespace) + if err != nil { + return nil, err + } + serverCert.KeyUsage = x509.KeyUsageDigitalSignature + serverCert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} + return serverCert, err +} + +// Actually sign the cert and return things in a form that we can use later on +func createCert(template, parent *x509.Certificate, pub interface{}, parentPriv interface{}) ( + cert *x509.Certificate, certPEM []byte, err error) { + + certDER, err := x509.CreateCertificate(rand.Reader, template, parent, pub, parentPriv) + if err != nil { + return + } + cert, err = x509.ParseCertificate(certDER) + if err != nil { + return + } + b := pem.Block{Type: "CERTIFICATE", Bytes: certDER} + certPEM = pem.EncodeToMemory(&b) + return +} + +func createCA(ctx context.Context, name, namespace string) (*rsa.PrivateKey, *x509.Certificate, []byte, error) { + logger := logging.FromContext(ctx) + rootKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + logger.Error("error generating random key", zap.Error(err)) + return nil, nil, nil, err + } + + rootCertTmpl, err := createCACertTemplate(name, namespace) + if err != nil { + logger.Error("error generating CA cert", zap.Error(err)) + return nil, nil, nil, err + } + + rootCert, rootCertPEM, err := createCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey) + if err != nil { + logger.Error("error signing the CA cert", zap.Error(err)) + return nil, nil, nil, err + } + return rootKey, rootCert, rootCertPEM, nil +} + +// CreateCerts creates and returns a CA certificate and certificate and +// key for the server. serverKey and serverCert are used by the server +// to establish trust for clients, CA certificate is used by the +// client to verify the server authentication chain. +func CreateCerts(ctx context.Context, name, namespace string) (serverKey, serverCert, caCert []byte, err error) { + logger := logging.FromContext(ctx) + // First create a CA certificate and private key + caKey, caCertificate, caCertificatePEM, err := createCA(ctx, name, namespace) + if err != nil { + return nil, nil, nil, err + } + + // Then create the private key for the serving cert + servKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + logger.Error("error generating random key", zap.Error(err)) + return nil, nil, nil, err + } + servCertTemplate, err := createServerCertTemplate(name, namespace) + if err != nil { + logger.Error("failed to create the server certificate template", zap.Error(err)) + return nil, nil, nil, err + } + + // create a certificate which wraps the server's public key, sign it with the CA private key + _, servCertPEM, err := createCert(servCertTemplate, caCertificate, &servKey.PublicKey, caKey) + if err != nil { + logger.Error("error signing server certificate template", zap.Error(err)) + return nil, nil, nil, err + } + servKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(servKey), + }) + return servKeyPEM, servCertPEM, caCertificatePEM, nil +} diff --git a/vendor/github.com/knative/pkg/webhook/webhook.go b/vendor/github.com/knative/pkg/webhook/webhook.go new file mode 100644 index 00000000000..2b1da8d95dd --- /dev/null +++ b/vendor/github.com/knative/pkg/webhook/webhook.go @@ -0,0 +1,646 @@ +/* +Copyright 2017 The Knative 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 webhook + +import ( + "bytes" + "context" + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "net/http" + "reflect" + "sort" + "strings" + "time" + + "go.uber.org/zap" + + "github.com/knative/pkg/apis" + "github.com/knative/pkg/logging" + "github.com/knative/pkg/logging/logkey" + + "github.com/mattbaird/jsonpatch" + admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + corev1 "k8s.io/api/core/v1" + v1beta1 "k8s.io/api/extensions/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes" + clientadmissionregistrationv1beta1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1" +) + +const ( + secretServerKey = "server-key.pem" + secretServerCert = "server-cert.pem" + secretCACert = "ca-cert.pem" +) + +var ( + deploymentKind = v1beta1.SchemeGroupVersion.WithKind("Deployment") + errMissingNewObject = errors.New("the new object may not be nil") +) + +// ControllerOptions contains the configuration for the webhook +type ControllerOptions struct { + // WebhookName is the name of the webhook we create to handle + // mutations before they get stored in the storage. + WebhookName string + + // ServiceName is the service name of the webhook. + ServiceName string + + // DeploymentName is the service name of the webhook. + DeploymentName string + + // SecretName is the name of k8s secret that contains the webhook + // server key/cert and corresponding CA cert that signed them. The + // server key/cert are used to serve the webhook and the CA cert + // is provided to k8s apiserver during admission controller + // registration. + SecretName string + + // Namespace is the namespace in which everything above lives + Namespace string + + // Port where the webhook is served. Per k8s admission + // registration requirements this should be 443 unless there is + // only a single port for the service. + Port int + + // RegistrationDelay controls how long admission registration + // occurs after the webhook is started. This is used to avoid + // potential races where registration completes and k8s apiserver + // invokes the webhook before the HTTP server is started. + RegistrationDelay time.Duration +} + +// ResourceCallback defines a signature for resource specific (Route, Configuration, etc.) +// handlers that can validate and mutate an object. If non-nil error is returned, object creation +// is denied. Mutations should be appended to the patches operations. +type ResourceCallback func(patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error + +// ResourceDefaulter defines a signature for resource specific (Route, Configuration, etc.) +// handlers that can set defaults on an object. If non-nil error is returned, object creation +// is denied. Mutations should be appended to the patches operations. +type ResourceDefaulter func(patches *[]jsonpatch.JsonPatchOperation, crd GenericCRD) error + +// AdmissionController implements the external admission webhook for validation of +// pilot configuration. +type AdmissionController struct { + Client kubernetes.Interface + Options ControllerOptions + GroupVersion schema.GroupVersion + Handlers map[string]runtime.Object + Logger *zap.SugaredLogger +} + +// GenericCRD is the interface definition that allows us to perform the generic +// CRD actions like deciding whether to increment generation and so forth. +type GenericCRD interface { + apis.Defaultable + apis.Validatable + + // GetObjectMeta return the object metadata + GetObjectMeta() metav1.Object + // GetGeneration returns the current Generation of the object + GetGeneration() int64 + // SetGeneration sets the Generation of the object + SetGeneration(int64) + // GetSpecJSON returns the Spec part of the resource marshalled into JSON + GetSpecJSON() ([]byte, error) +} + +// GetAPIServerExtensionCACert gets the Kubernetes aggregate apiserver +// client CA cert used by validator. +// +// NOTE: this certificate is provided kubernetes. We do not control +// its name or location. +func getAPIServerExtensionCACert(cl kubernetes.Interface) ([]byte, error) { + const name = "extension-apiserver-authentication" + c, err := cl.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + pem, ok := c.Data["requestheader-client-ca-file"] + if !ok { + return nil, fmt.Errorf("cannot find ca.crt in %v: ConfigMap.Data is %#v", name, c.Data) + } + return []byte(pem), nil +} + +// MakeTLSConfig makes a TLS configuration suitable for use with the server +func makeTLSConfig(serverCert, serverKey, caCert []byte) (*tls.Config, error) { + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + cert, err := tls.X509KeyPair(serverCert, serverKey) + if err != nil { + return nil, err + } + return &tls.Config{ + Certificates: []tls.Certificate{cert}, + ClientCAs: caCertPool, + ClientAuth: tls.NoClientCert, + // Note on GKE there apparently is no client cert sent, so this + // does not work on GKE. + // TODO: make this into a configuration option. + // ClientAuth: tls.RequireAndVerifyClientCert, + }, nil +} + +func getOrGenerateKeyCertsFromSecret(ctx context.Context, client kubernetes.Interface, + options *ControllerOptions) (serverKey, serverCert, caCert []byte, err error) { + logger := logging.FromContext(ctx) + secret, err := client.CoreV1().Secrets(options.Namespace).Get(options.SecretName, metav1.GetOptions{}) + if err != nil { + if !apierrors.IsNotFound(err) { + return nil, nil, nil, err + } + logger.Info("Did not find existing secret, creating one") + newSecret, err := generateSecret(ctx, options) + if err != nil { + return nil, nil, nil, err + } + secret, err = client.CoreV1().Secrets(newSecret.Namespace).Create(newSecret) + if err != nil && !apierrors.IsAlreadyExists(err) { + return nil, nil, nil, err + } + // Ok, so something else might have created, try fetching it one more time + secret, err = client.CoreV1().Secrets(options.Namespace).Get(options.SecretName, metav1.GetOptions{}) + if err != nil { + return nil, nil, nil, err + } + } + + var ok bool + if serverKey, ok = secret.Data[secretServerKey]; !ok { + return nil, nil, nil, errors.New("server key missing") + } + if serverCert, ok = secret.Data[secretServerCert]; !ok { + return nil, nil, nil, errors.New("server cert missing") + } + if caCert, ok = secret.Data[secretCACert]; !ok { + return nil, nil, nil, errors.New("ca cert missing") + } + return serverKey, serverCert, caCert, nil +} + +// Validate checks whether "new" and "old" implement HasImmutableFields and checks them, +// it then delegates validation to apis.Validatable on "new". +func Validate(ctx context.Context) ResourceCallback { + return func(patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error { + if hifNew, ok := new.(apis.Immutable); ok && old != nil { + hifOld, ok := old.(apis.Immutable) + if !ok { + return fmt.Errorf("unexpected type mismatch %T vs. %T", old, new) + } + if err := hifNew.CheckImmutableFields(hifOld); err != nil { + return err + } + } + // Can't just `return new.Validate()` because it doesn't properly nil-check. + if err := new.Validate(); err != nil { + return err + } + return nil + } +} + +// SetDefaults simply leverages apis.Defaultable to set defaults. +func SetDefaults(ctx context.Context) ResourceDefaulter { + return func(patches *[]jsonpatch.JsonPatchOperation, crd GenericCRD) error { + rawOriginal, err := json.Marshal(crd) + if err != nil { + return err + } + crd.SetDefaults() + + // Marshal the before and after. + rawAfter, err := json.Marshal(crd) + if err != nil { + return err + } + + patch, err := jsonpatch.CreatePatch(rawOriginal, rawAfter) + if err != nil { + return err + } + *patches = append(*patches, patch...) + return nil + } +} + +func configureCerts(ctx context.Context, client kubernetes.Interface, options *ControllerOptions) (*tls.Config, []byte, error) { + apiServerCACert, err := getAPIServerExtensionCACert(client) + if err != nil { + return nil, nil, err + } + serverKey, serverCert, caCert, err := getOrGenerateKeyCertsFromSecret( + ctx, client, options) + if err != nil { + return nil, nil, err + } + tlsConfig, err := makeTLSConfig(serverCert, serverKey, apiServerCACert) + if err != nil { + return nil, nil, err + } + return tlsConfig, caCert, nil +} + +// Run implements the admission controller run loop. +func (ac *AdmissionController) Run(stop <-chan struct{}) error { + logger := ac.Logger + ctx := logging.WithLogger(context.TODO(), logger) + tlsConfig, caCert, err := configureCerts(ctx, ac.Client, &ac.Options) + if err != nil { + logger.Error("Could not configure admission webhook certs", zap.Error(err)) + return err + } + + server := &http.Server{ + Handler: ac, + Addr: fmt.Sprintf(":%v", ac.Options.Port), + TLSConfig: tlsConfig, + } + + logger.Info("Found certificates for webhook...") + if ac.Options.RegistrationDelay != 0 { + logger.Infof("Delaying admission webhook registration for %v", ac.Options.RegistrationDelay) + } + + select { + case <-time.After(ac.Options.RegistrationDelay): + cl := ac.Client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations() + if err := ac.register(ctx, cl, caCert); err != nil { + logger.Error("Failed to register webhook", zap.Error(err)) + return err + } + defer func() { + if err := ac.unregister(ctx, cl); err != nil { + logger.Error("Failed to unregister webhook", zap.Error(err)) + } + }() + logger.Info("Successfully registered webhook") + case <-stop: + return nil + } + + go func() { + if err := server.ListenAndServeTLS("", ""); err != nil { + logger.Error("ListenAndServeTLS for admission webhook returned error", zap.Error(err)) + } + }() + <-stop + server.Close() // nolint: errcheck + return nil +} + +// Unregister unregisters the external admission webhook +func (ac *AdmissionController) unregister( + ctx context.Context, client clientadmissionregistrationv1beta1.MutatingWebhookConfigurationInterface) error { + logger := logging.FromContext(ctx) + logger.Info("Exiting..") + return nil +} + +// Register registers the external admission webhook for pilot +// configuration types. +func (ac *AdmissionController) register( + ctx context.Context, client clientadmissionregistrationv1beta1.MutatingWebhookConfigurationInterface, caCert []byte) error { // nolint: lll + logger := logging.FromContext(ctx) + failurePolicy := admissionregistrationv1beta1.Fail + + resources := sort.StringSlice{} + for k := range ac.Handlers { + // Lousy pluralizer + resources = append(resources, strings.ToLower(k)+"s") + } + resources.Sort() + + webhook := &admissionregistrationv1beta1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: ac.Options.WebhookName, + }, + Webhooks: []admissionregistrationv1beta1.Webhook{{ + Name: ac.Options.WebhookName, + Rules: []admissionregistrationv1beta1.RuleWithOperations{{ + Operations: []admissionregistrationv1beta1.OperationType{ + admissionregistrationv1beta1.Create, + admissionregistrationv1beta1.Update, + }, + Rule: admissionregistrationv1beta1.Rule{ + APIGroups: []string{ac.GroupVersion.Group}, + APIVersions: []string{ac.GroupVersion.Version}, + Resources: resources, + }, + }}, + ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{ + Service: &admissionregistrationv1beta1.ServiceReference{ + Namespace: ac.Options.Namespace, + Name: ac.Options.ServiceName, + }, + CABundle: caCert, + }, + FailurePolicy: &failurePolicy, + }}, + } + + // Set the owner to our deployment + deployment, err := ac.Client.ExtensionsV1beta1().Deployments(ac.Options.Namespace).Get(ac.Options.DeploymentName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("Failed to fetch our deployment: %s", err) + } + deploymentRef := metav1.NewControllerRef(deployment, deploymentKind) + webhook.OwnerReferences = append(webhook.OwnerReferences, *deploymentRef) + + // Try to create the webhook and if it already exists validate webhook rules + _, err = client.Create(webhook) + if err != nil { + if !apierrors.IsAlreadyExists(err) { + return fmt.Errorf("Failed to create a webhook: %s", err) + } + logger.Info("Webhook already exists") + configuredWebhook, err := client.Get(ac.Options.WebhookName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("Error retrieving webhook: %s", err) + } + if !reflect.DeepEqual(configuredWebhook.Webhooks, webhook.Webhooks) { + logger.Info("Updating webhook") + // Set the ResourceVersion as required by update. + webhook.ObjectMeta.ResourceVersion = configuredWebhook.ObjectMeta.ResourceVersion + if _, err := client.Update(webhook); err != nil { + return fmt.Errorf("Failed to update webhook: %s", err) + } + } else { + logger.Info("Webhook is already valid") + } + } else { + logger.Info("Created a webhook") + } + return nil +} + +// ServeHTTP implements the external admission webhook for mutating +// serving resources. +func (ac *AdmissionController) ServeHTTP(w http.ResponseWriter, r *http.Request) { + logger := ac.Logger + logger.Infof("Webhook ServeHTTP request=%#v", r) + + // verify the content type is accurate + contentType := r.Header.Get("Content-Type") + if contentType != "application/json" { + http.Error(w, "invalid Content-Type, want `application/json`", http.StatusUnsupportedMediaType) + return + } + + var review admissionv1beta1.AdmissionReview + defer r.Body.Close() + if err := json.NewDecoder(r.Body).Decode(&review); err != nil { + http.Error(w, fmt.Sprintf("could not decode body: %v", err), http.StatusBadRequest) + return + } + + logger = logger.With( + zap.String(logkey.Kind, fmt.Sprint(review.Request.Kind)), + zap.String(logkey.Namespace, review.Request.Namespace), + zap.String(logkey.Name, review.Request.Name), + zap.String(logkey.Operation, fmt.Sprint(review.Request.Operation)), + zap.String(logkey.Resource, fmt.Sprint(review.Request.Resource)), + zap.String(logkey.SubResource, fmt.Sprint(review.Request.SubResource)), + zap.String(logkey.UserInfo, fmt.Sprint(review.Request.UserInfo))) + reviewResponse := ac.admit(logging.WithLogger(r.Context(), logger), review.Request) + var response admissionv1beta1.AdmissionReview + if reviewResponse != nil { + response.Response = reviewResponse + response.Response.UID = review.Request.UID + } + + logger.Infof("AdmissionReview for %s: %v/%v response=%v", + review.Request.Kind, review.Request.Namespace, review.Request.Name, reviewResponse) + + if err := json.NewEncoder(w).Encode(response); err != nil { + http.Error(w, fmt.Sprintf("could encode response: %v", err), http.StatusInternalServerError) + return + } +} + +func makeErrorStatus(reason string, args ...interface{}) *admissionv1beta1.AdmissionResponse { + result := apierrors.NewBadRequest(fmt.Sprintf(reason, args...)).Status() + return &admissionv1beta1.AdmissionResponse{ + Result: &result, + Allowed: false, + } +} + +func (ac *AdmissionController) admit(ctx context.Context, request *admissionv1beta1.AdmissionRequest) *admissionv1beta1.AdmissionResponse { + logger := logging.FromContext(ctx) + switch request.Operation { + case admissionv1beta1.Create, admissionv1beta1.Update: + default: + logger.Infof("Unhandled webhook operation, letting it through %v", request.Operation) + return &admissionv1beta1.AdmissionResponse{Allowed: true} + } + + patchBytes, err := ac.mutate(ctx, request.Kind.Kind, request.OldObject.Raw, request.Object.Raw) + if err != nil { + return makeErrorStatus("mutation failed: %v", err) + } + logger.Infof("Kind: %q PatchBytes: %v", request.Kind, string(patchBytes)) + + return &admissionv1beta1.AdmissionResponse{ + Patch: patchBytes, + Allowed: true, + PatchType: func() *admissionv1beta1.PatchType { + pt := admissionv1beta1.PatchTypeJSONPatch + return &pt + }(), + } +} + +func (ac *AdmissionController) mutate(ctx context.Context, kind string, oldBytes []byte, newBytes []byte) ([]byte, error) { + logger := logging.FromContext(ctx) + handler, ok := ac.Handlers[kind] + if !ok { + logger.Errorf("Unhandled kind %q", kind) + return nil, fmt.Errorf("unhandled kind: %q", kind) + } + + oldObj := handler.DeepCopyObject().(GenericCRD) + newObj := handler.DeepCopyObject().(GenericCRD) + + if len(newBytes) != 0 { + newDecoder := json.NewDecoder(bytes.NewBuffer(newBytes)) + newDecoder.DisallowUnknownFields() + if err := newDecoder.Decode(&newObj); err != nil { + return nil, fmt.Errorf("cannot decode incoming new object: %v", err) + } + } else { + // Use nil to denote the absence of a new object (delete) + newObj = nil + } + + if len(oldBytes) != 0 { + oldDecoder := json.NewDecoder(bytes.NewBuffer(oldBytes)) + oldDecoder.DisallowUnknownFields() + if err := oldDecoder.Decode(&oldObj); err != nil { + return nil, fmt.Errorf("cannot decode incoming old object: %v", err) + } + } else { + // Use nil to denote the absence of an old object (create) + oldObj = nil + } + + var patches []jsonpatch.JsonPatchOperation + + err := updateGeneration(ctx, &patches, oldObj, newObj) + if err != nil { + logger.Error("Failed to update generation", zap.Error(err)) + return nil, fmt.Errorf("Failed to update generation: %s", err) + } + + if defaulter := SetDefaults(ctx); defaulter != nil { + if err := defaulter(&patches, newObj); err != nil { + logger.Error("Failed the resource specific defaulter", zap.Error(err)) + // Return the error message as-is to give the defaulter callback + // discretion over (our portion of) the message that the user sees. + return nil, err + } + } + + // None of the validators will accept a nil value for newObj. + if newObj == nil { + return nil, errMissingNewObject + } + if validator := Validate(ctx); validator != nil { + if err := validator(&patches, oldObj, newObj); err != nil { + logger.Error("Failed the resource specific validation", zap.Error(err)) + // Return the error message as-is to give the validation callback + // discretion over (our portion of) the message that the user sees. + return nil, err + } + } + + if err := validateMetadata(newObj); err != nil { + logger.Error("Failed to validate", zap.Error(err)) + return nil, fmt.Errorf("Failed to validate: %s", err) + } + return json.Marshal(patches) +} + +func validateMetadata(new GenericCRD) error { + name := new.GetObjectMeta().GetName() + + if strings.Contains(name, ".") { + return errors.New("Invalid resource name: special character . must not be present") + } + + if len(name) > 63 { + return errors.New("Invalid resource name: length must be no more than 63 characters") + } + return nil +} + +// updateGeneration sets the generation by following this logic: +// if there's no old object, it's create, set generation to 1 +// if there's an old object and spec has changed, set generation to oldGeneration + 1 +// appends the patch to patches if changes are necessary. +// TODO: Generation does not work correctly with CRD. They are scrubbed +// by the APIserver (https://github.com/kubernetes/kubernetes/issues/58778) +// So, we add Generation here. Once that gets fixed, remove this and use +// ObjectMeta.Generation instead. +func updateGeneration(ctx context.Context, patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error { + logger := logging.FromContext(ctx) + var oldGeneration int64 + if old == nil { + logger.Info("Old is nil") + } else { + oldGeneration = old.GetGeneration() + } + if oldGeneration == 0 { + logger.Info("Creating an object, setting generation to 1") + *patches = append(*patches, jsonpatch.JsonPatchOperation{ + Operation: "add", + Path: "/spec/generation", + Value: 1, + }) + return nil + } + + oldSpecJSON, err := old.GetSpecJSON() + if err != nil { + logger.Error("Failed to get Spec JSON for old", zap.Error(err)) + } + newSpecJSON, err := new.GetSpecJSON() + if err != nil { + logger.Error("Failed to get Spec JSON for new", zap.Error(err)) + } + + specPatches, err := jsonpatch.CreatePatch(oldSpecJSON, newSpecJSON) + if err != nil { + fmt.Printf("Error creating JSON patch:%v", err) + return err + } + if len(specPatches) > 0 { + specPatchesJSON, err := json.Marshal(specPatches) + if err != nil { + logger.Error("Failed to marshal spec patches", zap.Error(err)) + return err + } + logger.Infof("Specs differ:\n%+v\n", string(specPatchesJSON)) + + operation := "replace" + if newGeneration := new.GetGeneration(); newGeneration == 0 { + // If new is missing Generation, we need to "add" instead of "replace". + // We see this for Service resources because the initial generation is + // added to the managed Configuration and Route, but not the Service + // that manages them. + // TODO(#642): Remove this. + operation = "add" + } + *patches = append(*patches, jsonpatch.JsonPatchOperation{ + Operation: operation, + Path: "/spec/generation", + Value: oldGeneration + 1, + }) + return nil + } + logger.Info("No changes in the spec, not bumping generation") + return nil +} + +func generateSecret(ctx context.Context, options *ControllerOptions) (*corev1.Secret, error) { + serverKey, serverCert, caCert, err := CreateCerts(ctx, options.ServiceName, options.Namespace) + if err != nil { + return nil, err + } + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: options.SecretName, + Namespace: options.Namespace, + }, + Data: map[string][]byte{ + secretServerKey: serverKey, + secretServerCert: serverCert, + secretCACert: caCert, + }, + }, nil +} From d5f789e4cab4d22436ef887777140e1423274900 Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Wed, 1 Aug 2018 11:14:41 -0700 Subject: [PATCH 03/13] Revert webhook change. --- cmd/webhook/main.go | 57 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index 1c4c12cf350..fba298b0b29 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -1,6 +1,5 @@ /* -Copyright 2017 The Knative Authors - +Copyright 2018 The Knative 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 @@ -18,49 +17,49 @@ package main import ( "flag" - "log" - - "go.uber.org/zap" - - "github.com/knative/pkg/configmap" - "github.com/knative/pkg/logging" - "github.com/knative/pkg/logging/logkey" - "github.com/knative/pkg/signals" - "github.com/knative/pkg/webhook" + "github.com/golang/glog" "github.com/knative/eventing/pkg/system" + "github.com/knative/eventing/pkg/signals" + "github.com/knative/eventing/pkg/webhook" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) -const ( - logLevelKey = "webhook" -) - func main() { flag.Parse() - cm, err := configmap.Load("/etc/config-logging") - if err != nil { - log.Fatalf("Error loading logging configuration: %v", err) - } - config, err := logging.NewConfigFromMap(cm) - if err != nil { - log.Fatalf("Error parsing logging configuration: %v", err) - } - logger, atomicLevel := logging.NewLoggerFromConfig(config, logLevelKey) - defer logger.Sync() - logger = logger.With(zap.String(logkey.ControllerType, "webhook")) + defer glog.Flush() - logger.Info("Starting the Eventing Webhook") + glog.Info("Starting the Configuration Webhook") // set up signals so we handle the first shutdown signal gracefully stopCh := signals.SetupSignalHandler() clusterConfig, err := rest.InClusterConfig() if err != nil { - logger.Fatal("Failed to get in cluster config", zap.Error(err)) + glog.Fatal("Failed to get in cluster config", err) + } + + clientset, err := kubernetes.NewForConfig(clusterConfig) + if err != nil { + glog.Fatal("Failed to get the client set", err) + } + + options := webhook.ControllerOptions{ + ServiceName: "eventing-webhook", + ServiceNamespace: system.Namespace, + Port: 443, + SecretName: "eventing-webhook-certs", + WebhookName: "webhook.eventing.knative.dev", + } + controller, err := webhook.NewAdmissionController(clientset, options) + if err != nil { + glog.Fatal("Failed to create the admission controller", err) + } + controller.Run(stopCh) +} +g", zap.Error(err)) } kubeClient, err := kubernetes.NewForConfig(clusterConfig) From 6b093cfc64e3da1c8aa62b5519c8d6758571f8a9 Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Wed, 1 Aug 2018 11:15:12 -0700 Subject: [PATCH 04/13] Revert webhook change. --- cmd/webhook/main.go | 43 +------------------------------------------ 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index fba298b0b29..43c01f28e54 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -19,8 +19,8 @@ import ( "flag" "github.com/golang/glog" - "github.com/knative/eventing/pkg/system" "github.com/knative/eventing/pkg/signals" + "github.com/knative/eventing/pkg/system" "github.com/knative/eventing/pkg/webhook" "k8s.io/client-go/kubernetes" @@ -59,44 +59,3 @@ func main() { } controller.Run(stopCh) } -g", zap.Error(err)) - } - - kubeClient, err := kubernetes.NewForConfig(clusterConfig) - if err != nil { - logger.Fatal("Failed to get the client set", zap.Error(err)) - } - - // Watch the logging config map and dynamically update logging levels. - configMapWatcher := configmap.NewDefaultWatcher(kubeClient, system.Namespace) - configMapWatcher.Watch(logging.ConfigName, logging.UpdateLevelFromConfigMap(logger, atomicLevel, logLevelKey)) - if err = configMapWatcher.Start(stopCh); err != nil { - logger.Fatalf("failed to start configuration manager: %v", err) - } - - options := webhook.ControllerOptions{ - ServiceName: "eventing-webhook", - DeploymentName: "eventing-webhook", - Namespace: system.Namespace, - Port: 443, - SecretName: "eventing-webhook-certs", - WebhookName: "eventing-webhook.eventing.knative.dev", - } - controller := webhook.AdmissionController{ - Client: kubeClient, - Options: options, - // TODO(mattmoor): Will we need to rework these to support versioning? - GroupVersion: v1alpha1.SchemeGroupVersion, - Handlers: map[string]runtime.Object{ - "Revision": &v1alpha1.Revision{}, - "Configuration": &v1alpha1.Configuration{}, - "Route": &v1alpha1.Route{}, - "Service": &v1alpha1.Service{}, - }, - Logger: logger, - } - if err != nil { - logger.Fatal("Failed to create the admission controller", zap.Error(err)) - } - controller.Run(stopCh) -} From 1d4fbab6941fcf5080fba12a1c19daa1a51d3495 Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Wed, 1 Aug 2018 11:23:28 -0700 Subject: [PATCH 05/13] Add a test to make sure happy path is happy for getting logger in eventing. --- pkg/logging/logger_test.go | 42 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 pkg/logging/logger_test.go diff --git a/pkg/logging/logger_test.go b/pkg/logging/logger_test.go new file mode 100644 index 00000000000..8921985f0b8 --- /dev/null +++ b/pkg/logging/logger_test.go @@ -0,0 +1,42 @@ +/* +Copyright 2018 The Knative 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 logging + +import ( + "context" + "testing" + + "go.uber.org/zap" +) + +func TestContext(t *testing.T) { + logger1 := zap.NewNop().Sugar() + logger2 := zap.NewExample().Sugar() + + // Happy path tests + ctx := WithLogger(context.Background(), logger1) + checkFromContext(ctx, logger1, t) + + ctx = WithLogger(ctx, logger2) + checkFromContext(ctx, logger2, t) + + ctx = WithLogger(ctx, logger1) + checkFromContext(ctx, logger1, t) +} + +func checkFromContext(ctx context.Context, want *zap.SugaredLogger, t *testing.T) { + if got := FromContext(ctx); want != got { + t.Errorf("unexpected logger in context. want: %v, got: %v", want, got) + } +} From 11fbe8ad5c00d85331211232d8f74e37ed71b1a1 Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Wed, 1 Aug 2018 11:47:56 -0700 Subject: [PATCH 06/13] Adding config-logging to the main config. --- config/config-logging.yaml | 48 ++++++++++++++++++++++++ pkg/logging/testdata/config-logging.yaml | 7 ++-- 2 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 config/config-logging.yaml diff --git a/config/config-logging.yaml b/config/config-logging.yaml new file mode 100644 index 00000000000..c0df2b75dcc --- /dev/null +++ b/config/config-logging.yaml @@ -0,0 +1,48 @@ +# Copyright 2018 The Knative 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 +# +# https://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. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-logging + namespace: knative-eventing +data: + # Common configuration for all Knative codebase + zap-logger-config: | + { + "level": "info", + "development": false, + "outputPaths": ["stdout"], + "errorOutputPaths": ["stderr"], + "encoding": "json", + "encoderConfig": { + "timeKey": "ts", + "levelKey": "level", + "nameKey": "logger", + "callerKey": "caller", + "messageKey": "msg", + "stacktraceKey": "stacktrace", + "lineEnding": "", + "levelEncoder": "", + "timeEncoder": "iso8601", + "durationEncoder": "", + "callerEncoder": "" + } + } + + # Log level overrides + # For all components changes are be picked up immediately. + loglevel.controller: "info" + loglevel.controller-manager: "info" + loglevel.webhook: "info" diff --git a/pkg/logging/testdata/config-logging.yaml b/pkg/logging/testdata/config-logging.yaml index 895a0e7ed6b..fd36496653e 100644 --- a/pkg/logging/testdata/config-logging.yaml +++ b/pkg/logging/testdata/config-logging.yaml @@ -40,10 +40,9 @@ data: "callerEncoder": "" } } - + # Log level overrides - # For all components except the autoscaler and queue proxy, - # changes are be picked up immediately. + # For all components changes are be picked up immediately. loglevel.controller: "info" loglevel.controller-manager: "info" - loglevel.webhook: "info" + loglevel.webhook: "info" \ No newline at end of file From bdd83755c18f03aab4db736056a440d1e2dfe976 Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Wed, 1 Aug 2018 11:51:00 -0700 Subject: [PATCH 07/13] Remove owners file. --- pkg/logging/OWNERS | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 pkg/logging/OWNERS diff --git a/pkg/logging/OWNERS b/pkg/logging/OWNERS deleted file mode 100644 index f51b91be775..00000000000 --- a/pkg/logging/OWNERS +++ /dev/null @@ -1,6 +0,0 @@ -# The OWNERS file is used by prow to automatically merge approved PRs. - -approvers: -- mdemirhan -- n3wscott -- yanweiguo From 46bf0ea00e7a7afc50a41cab0f0a6aeb426ca13a Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Wed, 1 Aug 2018 12:39:08 -0700 Subject: [PATCH 08/13] Moving to just use named loggers in the controllers. --- cmd/controller-manager/main.go | 1 + cmd/webhook/main.go | 41 +++- config/500-controller-manager.yaml | 7 + config/500-controller.yaml | 7 + config/500-webhook.yaml | 9 +- pkg/logging/config.go | 56 ++--- pkg/logging/config_test.go | 276 ----------------------- pkg/logging/logger.go | 38 ---- pkg/logging/logger_test.go | 42 ---- pkg/logging/testdata/config-logging.yaml | 48 ---- 10 files changed, 76 insertions(+), 449 deletions(-) delete mode 100644 pkg/logging/config_test.go delete mode 100644 pkg/logging/logger.go delete mode 100644 pkg/logging/logger_test.go delete mode 100644 pkg/logging/testdata/config-logging.yaml diff --git a/cmd/controller-manager/main.go b/cmd/controller-manager/main.go index beab6651289..94dd5a062bf 100644 --- a/cmd/controller-manager/main.go +++ b/cmd/controller-manager/main.go @@ -44,6 +44,7 @@ type ProvideFunc func(manager.Manager) (controller.Controller, error) func main() { flag.Parse() + logf.SetLogger(logf.ZapLogger(false)) // Setup a Manager diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index 43c01f28e54..b676a6631c0 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -19,19 +19,44 @@ import ( "flag" "github.com/golang/glog" + eventinglogging "github.com/knative/eventing/pkg/logging" "github.com/knative/eventing/pkg/signals" "github.com/knative/eventing/pkg/system" "github.com/knative/eventing/pkg/webhook" + "github.com/knative/pkg/configmap" + "github.com/knative/pkg/logging" + "github.com/knative/pkg/logging/logkey" + "go.uber.org/zap" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + "log" +) + +const ( + logLevelKey = "webhook" + loggerName = "webhook" ) func main() { flag.Parse() - defer glog.Flush() - glog.Info("Starting the Configuration Webhook") + // Read the logging config and setup a logger. + cm, err := configmap.Load("/etc/config-logging") + if err != nil { + log.Fatalf("Error loading logging configuration: %v", err) + } + config, err := logging.NewConfigFromMap(cm, loggerName) + if err != nil { + log.Fatalf("Error parsing logging configuration: %v", err) + } + logger, atomicLevel := logging.NewLoggerFromConfig(config, logLevelKey) + defer logger.Sync() + logger = logger.With(zap.String(logkey.ControllerType, loggerName)) + + logger.Info("Starting the Eventing Webhook") + + defer glog.Flush() // set up signals so we handle the first shutdown signal gracefully stopCh := signals.SetupSignalHandler() @@ -41,11 +66,19 @@ func main() { glog.Fatal("Failed to get in cluster config", err) } - clientset, err := kubernetes.NewForConfig(clusterConfig) + kubeClient, err := kubernetes.NewForConfig(clusterConfig) if err != nil { glog.Fatal("Failed to get the client set", err) } + // Watch the logging config map and dynamically update logging levels. + configMapWatcher := configmap.NewDefaultWatcher(kubeClient, system.Namespace) + configMapWatcher.Watch(eventinglogging.ConfigName, logging.UpdateLevelFromConfigMap(logger, atomicLevel, logLevelKey)) + if err = configMapWatcher.Start(stopCh); err != nil { + logger.Fatalf("failed to start configuration manager: %v", err) + } + + // TODO(n3wscott): Send the logger to the controller. options := webhook.ControllerOptions{ ServiceName: "eventing-webhook", ServiceNamespace: system.Namespace, @@ -53,7 +86,7 @@ func main() { SecretName: "eventing-webhook-certs", WebhookName: "webhook.eventing.knative.dev", } - controller, err := webhook.NewAdmissionController(clientset, options) + controller, err := webhook.NewAdmissionController(kubeClient, options) if err != nil { glog.Fatal("Failed to create the admission controller", err) } diff --git a/config/500-controller-manager.yaml b/config/500-controller-manager.yaml index 3c42590391f..da8e26dc1ad 100644 --- a/config/500-controller-manager.yaml +++ b/config/500-controller-manager.yaml @@ -31,3 +31,10 @@ spec: "-logtostderr", "-stderrthreshold", "INFO", ] + volumeMounts: + - name: config-logging + mountPath: /etc/config-logging + volumes: + - name: config-logging + configMap: + name: config-logging \ No newline at end of file diff --git a/config/500-controller.yaml b/config/500-controller.yaml index 9078d8ce619..30b206299c6 100644 --- a/config/500-controller.yaml +++ b/config/500-controller.yaml @@ -33,3 +33,10 @@ spec: "-logtostderr", "-stderrthreshold", "INFO", ] + volumeMounts: + - name: config-logging + mountPath: /etc/config-logging + volumes: + - name: config-logging + configMap: + name: config-logging \ No newline at end of file diff --git a/config/500-webhook.yaml b/config/500-webhook.yaml index a057d08dc92..54dab75b6c4 100644 --- a/config/500-webhook.yaml +++ b/config/500-webhook.yaml @@ -32,4 +32,11 @@ spec: - name: eventing-webhook # This is the Go import path for the binary that is containerized # and substituted here. - image: github.com/knative/eventing/cmd/webhook \ No newline at end of file + image: github.com/knative/eventing/cmd/webhook + volumeMounts: + - name: config-logging + mountPath: /etc/config-logging + volumes: + - name: config-logging + configMap: + name: config-logging \ No newline at end of file diff --git a/pkg/logging/config.go b/pkg/logging/config.go index 8cf70b538e4..f59b1892a0a 100644 --- a/pkg/logging/config.go +++ b/pkg/logging/config.go @@ -16,46 +16,22 @@ limitations under the License. package logging -import ( - "go.uber.org/zap" - corev1 "k8s.io/api/core/v1" - - "github.com/knative/pkg/logging" -) - const ( + // ConfigName is the name of the config map used for knative-eventing logging config. ConfigName = "config-logging" -) -var components = []string{"controller", "controller-manager", "webhook"} - -// NewLogger creates a logger with the supplied configuration. -// In addition to the logger, it returns AtomicLevel that can -// be used to change the logging level at runtime. -// If configuration is empty, a fallback configuration is used. -// If configuration cannot be used to instantiate a logger, -// the same fallback configuration is used. -func NewLogger(configJSON string, levelOverride string) (*zap.SugaredLogger, zap.AtomicLevel) { - return logging.NewLogger(configJSON, levelOverride) -} - -// NewLoggerFromConfig creates a logger using the provided Config -func NewLoggerFromConfig(config *logging.Config, name string) (*zap.SugaredLogger, zap.AtomicLevel) { - return logging.NewLoggerFromConfig(config, name) -} - -// NewConfigFromMap creates a LoggingConfig from the supplied map -func NewConfigFromMap(data map[string]string) (*logging.Config, error) { - return logging.NewConfigFromMap(data, components...) -} - -// NewConfigFromConfigMap creates a LoggingConfig from the supplied ConfigMap -func NewConfigFromConfigMap(configMap *corev1.ConfigMap) (*logging.Config, error) { - return logging.NewConfigFromConfigMap(configMap, components...) -} - -// UpdateLevelFromConfigMap returns a helper func that can be used to update the logging level -// when a config map is updated -func UpdateLevelFromConfigMap(logger *zap.SugaredLogger, atomicLevel zap.AtomicLevel, levelKey string) func(configMap *corev1.ConfigMap) { - return logging.UpdateLevelFromConfigMap(logger, atomicLevel, levelKey, components...) -} + // Named Loggers are used to override the default log level. config-logging.yaml will use the follow: + // + // loglevel.controller: "info" + // loglevel.controller-manager: "info" + // loglevel.webhook: "info" + + // Controller is the name of the override key used inside of the logging config for Controller. + Controller = "controller" + + // ControllerManager is the name of the override key used inside of the logging config for Controller Manager. + ControllerManager = "controller-manager" + + // Webhook is the name of the override key used inside of the logging config for Webhook Controller. + Webhook = "webhook" +) diff --git a/pkg/logging/config_test.go b/pkg/logging/config_test.go deleted file mode 100644 index 01f6ef239a2..00000000000 --- a/pkg/logging/config_test.go +++ /dev/null @@ -1,276 +0,0 @@ -/* -Copyright 2018 The Knative 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 logging - -import ( - "fmt" - "io/ioutil" - "testing" - - "github.com/ghodss/yaml" - "github.com/knative/eventing/pkg/system" - "github.com/knative/pkg/logging" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestNewLogger(t *testing.T) { - logger, _ := NewLogger("", "") - if logger == nil { - t.Error("expected a non-nil logger") - } - - logger, _ = NewLogger("some invalid JSON here", "") - if logger == nil { - t.Error("expected a non-nil logger") - } - - logger, atomicLevel := NewLogger("", "debug") - if logger == nil { - t.Error("expected a non-nil logger") - } - if atomicLevel.Level() != zapcore.DebugLevel { - t.Error("expected level to be debug") - } - - // No good way to test if all the config is applied, - // but at the minimum, we can check and see if level is getting applied. - logger, atomicLevel = NewLogger("{\"level\": \"error\", \"outputPaths\": [\"stdout\"],\"errorOutputPaths\": [\"stderr\"],\"encoding\": \"json\"}", "") - if logger == nil { - t.Error("expected a non-nil logger") - } - if ce := logger.Desugar().Check(zap.InfoLevel, "test"); ce != nil { - t.Error("not expected to get info logs from the logger configured with error as min threshold") - } - if ce := logger.Desugar().Check(zap.ErrorLevel, "test"); ce == nil { - t.Error("expected to get error logs from the logger configured with error as min threshold") - } - if atomicLevel.Level() != zapcore.ErrorLevel { - t.Errorf("expected atomicLevel.Level() to be ErrorLevel but got %v.", atomicLevel.Level()) - } - - logger, atomicLevel = NewLogger("{\"level\": \"info\", \"outputPaths\": [\"stdout\"],\"errorOutputPaths\": [\"stderr\"],\"encoding\": \"json\"}", "") - if logger == nil { - t.Error("expected a non-nil logger") - } - if ce := logger.Desugar().Check(zap.DebugLevel, "test"); ce != nil { - t.Error("not expected to get debug logs from the logger configured with info as min threshold") - } - if ce := logger.Desugar().Check(zap.InfoLevel, "test"); ce == nil { - t.Error("expected to get info logs from the logger configured with info as min threshold") - } - if atomicLevel.Level() != zapcore.InfoLevel { - t.Errorf("expected atomicLevel.Level() to be InfoLevel but got %v.", atomicLevel.Level()) - } - - // Let's change the logging level using atomicLevel - atomicLevel.SetLevel(zapcore.ErrorLevel) - if ce := logger.Desugar().Check(zap.InfoLevel, "test"); ce != nil { - t.Error("not expected to get info logs from the logger configured with error as min threshold") - } - if ce := logger.Desugar().Check(zap.ErrorLevel, "test"); ce == nil { - t.Error("expected to get error logs from the logger configured with error as min threshold") - } - if atomicLevel.Level() != zapcore.ErrorLevel { - t.Errorf("expected atomicLevel.Level() to be ErrorLevel but got %v.", atomicLevel.Level()) - } - - // Test logging override - logger, _ = NewLogger("{\"level\": \"error\", \"outputPaths\": [\"stdout\"],\"errorOutputPaths\": [\"stderr\"],\"encoding\": \"json\"}", "info") - if logger == nil { - t.Error("expected a non-nil logger") - } - if ce := logger.Desugar().Check(zap.DebugLevel, "test"); ce != nil { - t.Error("not expected to get debug logs from the logger configured with info as min threshold") - } - if ce := logger.Desugar().Check(zap.InfoLevel, "test"); ce == nil { - t.Error("expected to get info logs from the logger configured with info as min threshold") - } - - // Invalid logging override - logger, _ = NewLogger("{\"level\": \"error\", \"outputPaths\": [\"stdout\"],\"errorOutputPaths\": [\"stderr\"],\"encoding\": \"json\"}", "randomstring") - if logger == nil { - t.Error("expected a non-nil logger") - } - if ce := logger.Desugar().Check(zap.InfoLevel, "test"); ce != nil { - t.Error("not expected to get info logs from the logger configured with error as min threshold") - } - if ce := logger.Desugar().Check(zap.ErrorLevel, "test"); ce == nil { - t.Error("expected to get error logs from the logger configured with error as min threshold") - } -} - -func TestNewConfigNoEntry(t *testing.T) { - c, err := NewConfigFromConfigMap(&corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: system.Namespace, - Name: "config-logging", - }, - }) - if err != nil { - t.Errorf("Expected no errors. got: %v", err) - } - if got, want := c.LoggingConfig, ""; got != want { - t.Errorf("LoggingConfig = %v, want %v", got, want) - } - if got, want := len(c.LoggingLevel), 0; got != want { - t.Errorf("len(LoggingLevel) = %v, want %v", got, want) - } -} - -func TestNewConfig(t *testing.T) { - wantCfg := "{\"level\": \"error\",\n\"outputPaths\": [\"stdout\"],\n\"errorOutputPaths\": [\"stderr\"],\n\"encoding\": \"json\"}" - wantLevel := zapcore.InfoLevel - c, err := NewConfigFromConfigMap(&corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: system.Namespace, - Name: "config-logging", - }, - Data: map[string]string{ - "zap-logger-config": wantCfg, - "loglevel.controller": wantLevel.String(), - }, - }) - if err != nil { - t.Errorf("Expected no errors. got: %v", err) - } - if got := c.LoggingConfig; got != wantCfg { - t.Errorf("LoggingConfig = %v, want %v", got, wantCfg) - } - if got := c.LoggingLevel["controller"]; got != wantLevel { - t.Errorf("LoggingLevel[controller] = %v, want %v", got, wantLevel) - } -} - -func TestOurConfig(t *testing.T) { - b, err := ioutil.ReadFile(fmt.Sprintf("testdata/%s.yaml", ConfigName)) - if err != nil { - t.Errorf("ReadFile() = %v", err) - } - var cm corev1.ConfigMap - if err := yaml.Unmarshal(b, &cm); err != nil { - t.Errorf("yaml.Unmarshal() = %v", err) - } - cfg, err := NewConfigFromConfigMap(&cm) - if err != nil { - t.Errorf("Expected no errors. got: %v", err) - } - if cfg == nil { - t.Errorf("NewConfigFromConfigMap() = %v, want non-nil", cfg) - } -} - -func TestNewLoggerFromConfig(t *testing.T) { - c, _, _ := getTestConfig() - _, atomicLevel := NewLoggerFromConfig(c, "controller") - if atomicLevel.Level() != zapcore.DebugLevel { - t.Errorf("logger level wanted: DebugLevel, got: %v", atomicLevel) - } -} - -func TestEmptyLevel(t *testing.T) { - c, err := NewConfigFromConfigMap(&corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: system.Namespace, - Name: "config-logging", - }, - Data: map[string]string{ - "zap-logger-config": "{\"level\": \"error\",\n\"outputPaths\": [\"stdout\"],\n\"errorOutputPaths\": [\"stderr\"],\n\"encoding\": \"json\"}", - "loglevel.controller": "", - }, - }) - if err != nil { - t.Errorf("Expected no errors. got: %v", err) - } - if _, ok := c.LoggingLevel["controller"]; ok { - t.Errorf("Expected nothing for LoggingLevel[controller]. got: %v", c.LoggingLevel["controller"]) - } -} - -func TestInvalidLevel(t *testing.T) { - wantCfg := "{\"level\": \"error\",\n\"outputPaths\": [\"stdout\"],\n\"errorOutputPaths\": [\"stderr\"],\n\"encoding\": \"json\"}" - _, err := NewConfigFromConfigMap(&corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: system.Namespace, - Name: "config-logging", - }, - Data: map[string]string{ - "zap-logger-config": wantCfg, - "loglevel.controller": "invalid", - }, - }) - if err == nil { - t.Errorf("Expected errors. got nothing") - } -} - -func getTestConfig() (*logging.Config, string, string) { - wantCfg := "{\"level\": \"error\",\n\"outputPaths\": [\"stdout\"],\n\"errorOutputPaths\": [\"stderr\"],\n\"encoding\": \"json\"}" - wantLevel := "debug" - c, _ := NewConfigFromConfigMap(&corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: system.Namespace, - Name: "config-logging", - }, - Data: map[string]string{ - "zap-logger-config": wantCfg, - "loglevel.controller": wantLevel, - }, - }) - return c, wantCfg, wantLevel -} - -func TestUpdateLevelFromConfigMap(t *testing.T) { - logger, atomicLevel := NewLogger("", "debug") - want := zapcore.DebugLevel - if atomicLevel.Level() != zapcore.DebugLevel { - t.Fatalf("Expected initial logger level to %v, got: %v", want, atomicLevel.Level()) - } - - cm := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: system.Namespace, - Name: "config-logging", - }, - Data: map[string]string{ - "zap-logger-config": "", - "loglevel.controller": "panic", - }, - } - - tests := []struct { - setLevel string - wantLevel zapcore.Level - }{ - {"info", zapcore.InfoLevel}, - {"error", zapcore.ErrorLevel}, - {"invalid", zapcore.ErrorLevel}, - {"debug", zapcore.DebugLevel}, - {"debug", zapcore.DebugLevel}, - } - - u := UpdateLevelFromConfigMap(logger, atomicLevel, "controller") - for _, tt := range tests { - cm.Data["loglevel.controller"] = tt.setLevel - u(cm) - if atomicLevel.Level() != tt.wantLevel { - t.Errorf("Invalid logging level. want: %v, got: %v", tt.wantLevel, atomicLevel.Level()) - } - } -} diff --git a/pkg/logging/logger.go b/pkg/logging/logger.go deleted file mode 100644 index 843306c37c5..00000000000 --- a/pkg/logging/logger.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2018 The Knative 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 logging - -import ( - "context" - - "go.uber.org/zap" - - "github.com/knative/pkg/logging" -) - -// WithLogger returns a copy of parent context in which the -// value associated with logger key is the supplied logger. -func WithLogger(ctx context.Context, logger *zap.SugaredLogger) context.Context { - return logging.WithLogger(ctx, logger) -} - -// FromContext returns the logger stored in context. -// Returns nil if no logger is set in context, or if the stored value is -// not of correct type. -func FromContext(ctx context.Context) *zap.SugaredLogger { - return logging.FromContext(ctx) -} diff --git a/pkg/logging/logger_test.go b/pkg/logging/logger_test.go deleted file mode 100644 index 8921985f0b8..00000000000 --- a/pkg/logging/logger_test.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2018 The Knative 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 logging - -import ( - "context" - "testing" - - "go.uber.org/zap" -) - -func TestContext(t *testing.T) { - logger1 := zap.NewNop().Sugar() - logger2 := zap.NewExample().Sugar() - - // Happy path tests - ctx := WithLogger(context.Background(), logger1) - checkFromContext(ctx, logger1, t) - - ctx = WithLogger(ctx, logger2) - checkFromContext(ctx, logger2, t) - - ctx = WithLogger(ctx, logger1) - checkFromContext(ctx, logger1, t) -} - -func checkFromContext(ctx context.Context, want *zap.SugaredLogger, t *testing.T) { - if got := FromContext(ctx); want != got { - t.Errorf("unexpected logger in context. want: %v, got: %v", want, got) - } -} diff --git a/pkg/logging/testdata/config-logging.yaml b/pkg/logging/testdata/config-logging.yaml deleted file mode 100644 index fd36496653e..00000000000 --- a/pkg/logging/testdata/config-logging.yaml +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2018 The Knative 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 -# -# https://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. - -apiVersion: v1 -kind: ConfigMap -metadata: - name: config-logging - namespace: knative-eventing -data: - # Common configuration for all Knative codebase - zap-logger-config: | - { - "level": "info", - "development": false, - "outputPaths": ["stdout"], - "errorOutputPaths": ["stderr"], - "encoding": "json", - "encoderConfig": { - "timeKey": "ts", - "levelKey": "level", - "nameKey": "logger", - "callerKey": "caller", - "messageKey": "msg", - "stacktraceKey": "stacktrace", - "lineEnding": "", - "levelEncoder": "", - "timeEncoder": "iso8601", - "durationEncoder": "", - "callerEncoder": "" - } - } - - # Log level overrides - # For all components changes are be picked up immediately. - loglevel.controller: "info" - loglevel.controller-manager: "info" - loglevel.webhook: "info" \ No newline at end of file From 2743412b984619c1e49a82d64c45fc9781cace44 Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Wed, 1 Aug 2018 12:41:05 -0700 Subject: [PATCH 09/13] Use logging config for controller name. --- cmd/webhook/main.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index b676a6631c0..1279acfc15b 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -33,11 +33,6 @@ import ( "log" ) -const ( - logLevelKey = "webhook" - loggerName = "webhook" -) - func main() { flag.Parse() @@ -46,13 +41,13 @@ func main() { if err != nil { log.Fatalf("Error loading logging configuration: %v", err) } - config, err := logging.NewConfigFromMap(cm, loggerName) + config, err := logging.NewConfigFromMap(cm, eventinglogging.Webhook) if err != nil { log.Fatalf("Error parsing logging configuration: %v", err) } - logger, atomicLevel := logging.NewLoggerFromConfig(config, logLevelKey) + logger, atomicLevel := logging.NewLoggerFromConfig(config, eventinglogging.Webhook) defer logger.Sync() - logger = logger.With(zap.String(logkey.ControllerType, loggerName)) + logger = logger.With(zap.String(logkey.ControllerType, eventinglogging.Webhook)) logger.Info("Starting the Eventing Webhook") @@ -73,7 +68,7 @@ func main() { // Watch the logging config map and dynamically update logging levels. configMapWatcher := configmap.NewDefaultWatcher(kubeClient, system.Namespace) - configMapWatcher.Watch(eventinglogging.ConfigName, logging.UpdateLevelFromConfigMap(logger, atomicLevel, logLevelKey)) + configMapWatcher.Watch(eventinglogging.ConfigName, logging.UpdateLevelFromConfigMap(logger, atomicLevel, eventinglogging.Webhook, eventinglogging.Webhook)) if err = configMapWatcher.Start(stopCh); err != nil { logger.Fatalf("failed to start configuration manager: %v", err) } From 9d68d8637499e6c857f8017652b51232c2bad23a Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Wed, 1 Aug 2018 12:43:14 -0700 Subject: [PATCH 10/13] Add newlines to the yaml. --- config/500-controller-manager.yaml | 2 +- config/500-controller.yaml | 2 +- config/500-webhook.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/500-controller-manager.yaml b/config/500-controller-manager.yaml index da8e26dc1ad..6adbce899a6 100644 --- a/config/500-controller-manager.yaml +++ b/config/500-controller-manager.yaml @@ -37,4 +37,4 @@ spec: volumes: - name: config-logging configMap: - name: config-logging \ No newline at end of file + name: config-logging diff --git a/config/500-controller.yaml b/config/500-controller.yaml index 30b206299c6..0881ba1fa76 100644 --- a/config/500-controller.yaml +++ b/config/500-controller.yaml @@ -39,4 +39,4 @@ spec: volumes: - name: config-logging configMap: - name: config-logging \ No newline at end of file + name: config-logging diff --git a/config/500-webhook.yaml b/config/500-webhook.yaml index 54dab75b6c4..be288e52517 100644 --- a/config/500-webhook.yaml +++ b/config/500-webhook.yaml @@ -39,4 +39,4 @@ spec: volumes: - name: config-logging configMap: - name: config-logging \ No newline at end of file + name: config-logging From 09db04a59052eea84013996b794248045df8e62e Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Wed, 1 Aug 2018 13:03:03 -0700 Subject: [PATCH 11/13] Adding logging creation and config reading to the controllers now. --- Gopkg.lock | 9 +- cmd/controller-manager/main.go | 54 +- cmd/controller/main.go | 30 + cmd/webhook/main.go | 24 +- .../apis/channels/logkey/constants.go | 20 +- .../feeds}/logkey/constants.go | 12 - .../apis/flows/logkey/constants.go | 11 +- pkg/{logging => logconfig}/config.go | 2 +- .../github.com/knative/pkg/apis/defaults.go | 23 - .../knative/pkg/apis/field_error.go | 110 --- .../github.com/knative/pkg/signals/signal.go | 43 -- .../github.com/knative/pkg/webhook/certs.go | 161 ----- .../github.com/knative/pkg/webhook/webhook.go | 646 ------------------ 13 files changed, 112 insertions(+), 1033 deletions(-) rename vendor/github.com/knative/pkg/signals/signal_posix.go => pkg/apis/channels/logkey/constants.go (62%) rename pkg/{logging => apis/feeds}/logkey/constants.go (76%) rename vendor/github.com/knative/pkg/signals/signal_windows.go => pkg/apis/flows/logkey/constants.go (81%) rename pkg/{logging => logconfig}/config.go (98%) delete mode 100644 vendor/github.com/knative/pkg/apis/defaults.go delete mode 100644 vendor/github.com/knative/pkg/apis/field_error.go delete mode 100644 vendor/github.com/knative/pkg/signals/signal.go delete mode 100644 vendor/github.com/knative/pkg/webhook/certs.go delete mode 100644 vendor/github.com/knative/pkg/webhook/webhook.go diff --git a/Gopkg.lock b/Gopkg.lock index a9bd1cb1152..367b6fd65a9 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -262,15 +262,12 @@ [[projects]] branch = "master" - digest = "1:d2ad0b894debee75e84ccbb964a26b77fbfbd983e0e2f36242fc3ba028d5c3ac" + digest = "1:142c93e2290b002f67a0540ceaf3f94acf691e9fbd202fe7cb5429f80a5f5c0b" name = "github.com/knative/pkg" packages = [ - "apis", "configmap", "logging", "logging/logkey", - "signals", - "webhook", ] pruneopts = "NUT" revision = "fdf2fdcd93ddadf126cad6991d43a90f20033ceb" @@ -1015,7 +1012,6 @@ "github.com/Shopify/sarama", "github.com/bsm/sarama-cluster", "github.com/davecgh/go-spew/spew", - "github.com/ghodss/yaml", "github.com/golang/glog", "github.com/google/go-cmp/cmp", "github.com/google/go-cmp/cmp/cmpopts", @@ -1025,8 +1021,6 @@ "github.com/knative/pkg/configmap", "github.com/knative/pkg/logging", "github.com/knative/pkg/logging/logkey", - "github.com/knative/pkg/signals", - "github.com/knative/pkg/webhook", "github.com/knative/serving/pkg/apis/istio/v1alpha3", "github.com/knative/serving/pkg/apis/serving/v1alpha1", "github.com/knative/serving/pkg/client/clientset/versioned", @@ -1035,7 +1029,6 @@ "github.com/mattbaird/jsonpatch", "github.com/prometheus/client_golang/prometheus/promhttp", "go.uber.org/zap", - "go.uber.org/zap/zapcore", "golang.org/x/net/context", "golang.org/x/oauth2", "google.golang.org/grpc/codes", diff --git a/cmd/controller-manager/main.go b/cmd/controller-manager/main.go index 94dd5a062bf..d586dd99028 100644 --- a/cmd/controller-manager/main.go +++ b/cmd/controller-manager/main.go @@ -25,11 +25,23 @@ import ( flowsv1alpha1 "github.com/knative/eventing/pkg/apis/flows/v1alpha1" "github.com/knative/eventing/pkg/controller/feed" "github.com/knative/eventing/pkg/controller/flow" + "github.com/knative/eventing/pkg/logconfig" + + "github.com/knative/pkg/configmap" + "github.com/knative/pkg/logging" + "github.com/knative/pkg/logging/logkey" + istiov1alpha3 "github.com/knative/serving/pkg/apis/istio/v1alpha3" servingv1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1" + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - "sigs.k8s.io/controller-runtime/pkg/client/config" + "k8s.io/client-go/rest" + + "github.com/knative/eventing/pkg/system" + clientconfig "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/manager" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" @@ -45,10 +57,45 @@ type ProvideFunc func(manager.Manager) (controller.Controller, error) func main() { flag.Parse() + // Read the logging config and setup a logger. + cm, err := configmap.Load("/etc/config-logging") + if err != nil { + log.Fatalf("Error loading logging configuration: %v", err) + } + config, err := logging.NewConfigFromMap(cm, logconfig.ControllerManager) + if err != nil { + log.Fatalf("Error parsing logging configuration: %v", err) + } + logger, atomicLevel := logging.NewLoggerFromConfig(config, logconfig.ControllerManager) + defer logger.Sync() + logger = logger.With(zap.String(logkey.ControllerType, logconfig.ControllerManager)) + + logger.Info("Starting the Controller Manager") + logf.SetLogger(logf.ZapLogger(false)) + // set up signals so we handle the first shutdown signal gracefully + stopCh := signals.SetupSignalHandler() + + clusterConfig, err := rest.InClusterConfig() + if err != nil { + logger.Fatal("Failed to get in cluster config", err) + } + + kubeClient, err := kubernetes.NewForConfig(clusterConfig) + if err != nil { + logger.Fatal("Failed to get the client set", err) + } + + // Watch the logging config map and dynamically update logging levels. + configMapWatcher := configmap.NewDefaultWatcher(kubeClient, system.Namespace) + configMapWatcher.Watch(logconfig.ConfigName, logging.UpdateLevelFromConfigMap(logger, atomicLevel, logconfig.ControllerManager, logconfig.ControllerManager)) + if err = configMapWatcher.Start(stopCh); err != nil { + logger.Fatalf("failed to start controller manager configmap watcher: %v", err) + } + // Setup a Manager - mrg, err := manager.New(config.GetConfigOrDie(), manager.Options{}) + mrg, err := manager.New(clientconfig.GetConfigOrDie(), manager.Options{}) if err != nil { log.Fatal(err) } @@ -73,11 +120,12 @@ func main() { flow.ProvideController, } + // TODO(n3wscott): Send the logger to the controllers. for _, provider := range providers { if _, err := provider(mrg); err != nil { log.Fatal(err) } } - log.Fatal(mrg.Start(signals.SetupSignalHandler())) + log.Fatal(mrg.Start(stopCh)) } diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 7ca6591b23d..3e8f0f9454c 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -41,7 +41,14 @@ import ( "github.com/knative/eventing/pkg/controller/clusterbus" "github.com/knative/eventing/pkg/signals" + "github.com/knative/eventing/pkg/logconfig" + "github.com/knative/eventing/pkg/system" + "github.com/knative/pkg/configmap" + "github.com/knative/pkg/logging" + "github.com/knative/pkg/logging/logkey" "github.com/prometheus/client_golang/prometheus/promhttp" + "go.uber.org/zap" + "log" ) const ( @@ -58,6 +65,21 @@ var ( func main() { flag.Parse() + // Read the logging config and setup a logger. + cm, err := configmap.Load("/etc/config-logging") + if err != nil { + log.Fatalf("Error loading logging configuration: %v", err) + } + config, err := logging.NewConfigFromMap(cm, logconfig.Controller) + if err != nil { + log.Fatalf("Error parsing logging configuration: %v", err) + } + logger, atomicLevel := logging.NewLoggerFromConfig(config, logconfig.Controller) + defer logger.Sync() + logger = logger.With(zap.String(logkey.ControllerType, logconfig.Controller)) + + logger.Info("Starting the Controller") + // set up signals so we handle the first shutdown signal gracefully stopCh := signals.SetupSignalHandler() @@ -94,6 +116,13 @@ func main() { informerFactory := informers.NewSharedInformerFactory(client, time.Second*30) servingInformerFactory := servinginformers.NewSharedInformerFactory(servingClient, time.Second*30) + // Watch the logging config map and dynamically update logging levels. + configMapWatcher := configmap.NewDefaultWatcher(kubeClient, system.Namespace) + configMapWatcher.Watch(logconfig.ConfigName, logging.UpdateLevelFromConfigMap(logger, atomicLevel, logconfig.Controller, logconfig.Controller)) + if err = configMapWatcher.Start(stopCh); err != nil { + logger.Fatalf("failed to start controller config map watcher: %v", err) + } + // Add new controllers here. ctors := []controller.Constructor{ bus.NewController, @@ -101,6 +130,7 @@ func main() { channel.NewController, } + // TODO(n3wscott): Send the logger to the controllers. // Build all of our controllers, with the clients constructed above. controllers := make([]controller.Interface, 0, len(ctors)) for _, ctor := range ctors { diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index 1279acfc15b..73b001db544 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -17,9 +17,9 @@ package main import ( "flag" + "log" - "github.com/golang/glog" - eventinglogging "github.com/knative/eventing/pkg/logging" + "github.com/knative/eventing/pkg/logconfig" "github.com/knative/eventing/pkg/signals" "github.com/knative/eventing/pkg/system" "github.com/knative/eventing/pkg/webhook" @@ -27,10 +27,10 @@ import ( "github.com/knative/pkg/configmap" "github.com/knative/pkg/logging" "github.com/knative/pkg/logging/logkey" + "go.uber.org/zap" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" - "log" ) func main() { @@ -41,36 +41,34 @@ func main() { if err != nil { log.Fatalf("Error loading logging configuration: %v", err) } - config, err := logging.NewConfigFromMap(cm, eventinglogging.Webhook) + config, err := logging.NewConfigFromMap(cm, logconfig.Webhook) if err != nil { log.Fatalf("Error parsing logging configuration: %v", err) } - logger, atomicLevel := logging.NewLoggerFromConfig(config, eventinglogging.Webhook) + logger, atomicLevel := logging.NewLoggerFromConfig(config, logconfig.Webhook) defer logger.Sync() - logger = logger.With(zap.String(logkey.ControllerType, eventinglogging.Webhook)) + logger = logger.With(zap.String(logkey.ControllerType, logconfig.Webhook)) logger.Info("Starting the Eventing Webhook") - defer glog.Flush() - // set up signals so we handle the first shutdown signal gracefully stopCh := signals.SetupSignalHandler() clusterConfig, err := rest.InClusterConfig() if err != nil { - glog.Fatal("Failed to get in cluster config", err) + logger.Fatal("Failed to get in cluster config", err) } kubeClient, err := kubernetes.NewForConfig(clusterConfig) if err != nil { - glog.Fatal("Failed to get the client set", err) + logger.Fatal("Failed to get the client set", err) } // Watch the logging config map and dynamically update logging levels. configMapWatcher := configmap.NewDefaultWatcher(kubeClient, system.Namespace) - configMapWatcher.Watch(eventinglogging.ConfigName, logging.UpdateLevelFromConfigMap(logger, atomicLevel, eventinglogging.Webhook, eventinglogging.Webhook)) + configMapWatcher.Watch(logconfig.ConfigName, logging.UpdateLevelFromConfigMap(logger, atomicLevel, logconfig.Webhook, logconfig.Webhook)) if err = configMapWatcher.Start(stopCh); err != nil { - logger.Fatalf("failed to start configuration manager: %v", err) + logger.Fatalf("failed to start webhook configmap watcher: %v", err) } // TODO(n3wscott): Send the logger to the controller. @@ -83,7 +81,7 @@ func main() { } controller, err := webhook.NewAdmissionController(kubeClient, options) if err != nil { - glog.Fatal("Failed to create the admission controller", err) + logger.Fatal("Failed to create the admission controller", err) } controller.Run(stopCh) } diff --git a/vendor/github.com/knative/pkg/signals/signal_posix.go b/pkg/apis/channels/logkey/constants.go similarity index 62% rename from vendor/github.com/knative/pkg/signals/signal_posix.go rename to pkg/apis/channels/logkey/constants.go index b3537d0e5dc..f0071154a8e 100644 --- a/vendor/github.com/knative/pkg/signals/signal_posix.go +++ b/pkg/apis/channels/logkey/constants.go @@ -1,5 +1,3 @@ -// +build !windows - /* Copyright 2018 The Knative Authors @@ -16,11 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package signals +package logkey -import ( - "os" - "syscall" -) +const ( + kNative = "knative.dev/" + + // ClusterBus is the key used for cluster scoped bus name in structured logs + ClusterBus = kNative + "clusterbus" -var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM} + // Bus is the key used for bus name in structured logs + Bus = kNative + "bus" + + // Channel is the key used for channel name in structured logs + Channel = kNative + "channel" +) diff --git a/pkg/logging/logkey/constants.go b/pkg/apis/feeds/logkey/constants.go similarity index 76% rename from pkg/logging/logkey/constants.go rename to pkg/apis/feeds/logkey/constants.go index 27e1f7a273f..4bfb3cb2fdc 100644 --- a/pkg/logging/logkey/constants.go +++ b/pkg/apis/feeds/logkey/constants.go @@ -33,16 +33,4 @@ const ( // Feed is the key used for feed name in structured logs Feed = kNative + "feed" - - // Flow is the key used for flow name in structured logs - Flow = kNative + "flow" - - // ClusterBus is the key used for cluster scoped bus name in structured logs - ClusterBus = kNative + "clusterbus" - - // Bus is the key used for bus name in structured logs - Bus = kNative + "bus" - - // Channel is the key used for channel name in structured logs - Channel = kNative + "channel" ) diff --git a/vendor/github.com/knative/pkg/signals/signal_windows.go b/pkg/apis/flows/logkey/constants.go similarity index 81% rename from vendor/github.com/knative/pkg/signals/signal_windows.go rename to pkg/apis/flows/logkey/constants.go index a5a4026faad..1136af2097b 100644 --- a/vendor/github.com/knative/pkg/signals/signal_windows.go +++ b/pkg/apis/flows/logkey/constants.go @@ -14,10 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -package signals +package logkey -import ( - "os" -) +const ( + kNative = "knative.dev/" -var shutdownSignals = []os.Signal{os.Interrupt} + // Flow is the key used for flow name in structured logs + Flow = kNative + "flow" +) diff --git a/pkg/logging/config.go b/pkg/logconfig/config.go similarity index 98% rename from pkg/logging/config.go rename to pkg/logconfig/config.go index f59b1892a0a..f1f5237b767 100644 --- a/pkg/logging/config.go +++ b/pkg/logconfig/config.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package logging +package logconfig const ( // ConfigName is the name of the config map used for knative-eventing logging config. diff --git a/vendor/github.com/knative/pkg/apis/defaults.go b/vendor/github.com/knative/pkg/apis/defaults.go deleted file mode 100644 index 20893f8e510..00000000000 --- a/vendor/github.com/knative/pkg/apis/defaults.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2017 The Knative 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 apis - -// Defaultable defines an interface for setting the defaults for the -// uninitialized fields of this instance. -type Defaultable interface { - SetDefaults() -} diff --git a/vendor/github.com/knative/pkg/apis/field_error.go b/vendor/github.com/knative/pkg/apis/field_error.go deleted file mode 100644 index 079041a251e..00000000000 --- a/vendor/github.com/knative/pkg/apis/field_error.go +++ /dev/null @@ -1,110 +0,0 @@ -/* -Copyright 2017 The Knative 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 apis - -import ( - "fmt" - "strings" -) - -// CurrentField is a constant to supply as a fieldPath for when there is -// a problem with the current field itself. -const CurrentField = "" - -// FieldError is used to propagate the context of errors pertaining to -// specific fields in a manner suitable for use in a recursive walk, so -// that errors contain the appropriate field context. -// +k8s:deepcopy-gen=false -type FieldError struct { - Message string - Paths []string - // Details contains an optional longer payload. - Details string -} - -// FieldError implements error -var _ error = (*FieldError)(nil) - -// Validatable indicates that a particular type may have its fields validated. -type Validatable interface { - // Validate checks the validity of this types fields. - Validate() *FieldError -} - -// Immutable indicates that a particular type has fields that should -// not change after creation. -type Immutable interface { - // CheckImmutableFields checks that the current instance's immutable - // fields haven't changed from the provided original. - CheckImmutableFields(original Immutable) *FieldError -} - -// ViaField is used to propagate a validation error along a field access. -// For example, if a type recursively validates its "spec" via: -// if err := foo.Spec.Validate(); err != nil { -// // Augment any field paths with the context that they were accessed -// // via "spec". -// return err.ViaField("spec") -// } -func (fe *FieldError) ViaField(prefix ...string) *FieldError { - if fe == nil { - return nil - } - var newPaths []string - for _, oldPath := range fe.Paths { - if oldPath == CurrentField { - newPaths = append(newPaths, strings.Join(prefix, ".")) - } else { - newPaths = append(newPaths, - strings.Join(append(prefix, oldPath), ".")) - } - } - fe.Paths = newPaths - return fe -} - -// Error implements error -func (fe *FieldError) Error() string { - if fe.Details == "" { - return fmt.Sprintf("%v: %v", fe.Message, strings.Join(fe.Paths, ", ")) - } - return fmt.Sprintf("%v: %v\n%v", fe.Message, strings.Join(fe.Paths, ", "), fe.Details) -} - -// ErrMissingField is a variadic helper method for constructing a FieldError for a set of missing fields. -func ErrMissingField(fieldPaths ...string) *FieldError { - return &FieldError{ - Message: "missing field(s)", - Paths: fieldPaths, - } -} - -// ErrDisallowedFields is a variadic helper method for constructing a FieldError for a set of disallowed fields. -func ErrDisallowedFields(fieldPaths ...string) *FieldError { - return &FieldError{ - Message: "must not set the field(s)", - Paths: fieldPaths, - } -} - -// ErrInvalidValue constructs a FieldError for a field that has received an invalid string value. -func ErrInvalidValue(value string, fieldPath string) *FieldError { - return &FieldError{ - Message: fmt.Sprintf("invalid value %q", value), - Paths: []string{fieldPath}, - } -} diff --git a/vendor/github.com/knative/pkg/signals/signal.go b/vendor/github.com/knative/pkg/signals/signal.go deleted file mode 100644 index bb9d8c3f893..00000000000 --- a/vendor/github.com/knative/pkg/signals/signal.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2018 The Knative 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 signals - -import ( - "os" - "os/signal" -) - -var onlyOneSignalHandler = make(chan struct{}) - -// SetupSignalHandler registered for SIGTERM and SIGINT. A stop channel is returned -// which is closed on one of these signals. If a second signal is caught, the program -// is terminated with exit code 1. -func SetupSignalHandler() (stopCh <-chan struct{}) { - close(onlyOneSignalHandler) // panics when called twice - - stop := make(chan struct{}) - c := make(chan os.Signal, 2) - signal.Notify(c, shutdownSignals...) - go func() { - <-c - close(stop) - <-c - os.Exit(1) // second signal. Exit directly. - }() - - return stop -} diff --git a/vendor/github.com/knative/pkg/webhook/certs.go b/vendor/github.com/knative/pkg/webhook/certs.go deleted file mode 100644 index 8bc80b4f4ef..00000000000 --- a/vendor/github.com/knative/pkg/webhook/certs.go +++ /dev/null @@ -1,161 +0,0 @@ -/* -Copyright 2018 The Knative 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 webhook - -import ( - "context" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "errors" - "math/big" - "time" - - "go.uber.org/zap" - - "github.com/knative/pkg/logging" -) - -const ( - // TODO(vaikas): Add / change other parts of the cert we might care about - organization = "kube" -) - -// Create the common parts of the cert. These don't change between -// the root/CA cert and the server cert. -func createCertTemplate(name, namespace string) (*x509.Certificate, error) { - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return nil, errors.New("failed to generate serial number: " + err.Error()) - } - - serviceName := name + "." + namespace - serviceNames := []string{serviceName, serviceName + ".svc", serviceName + ".svc.cluster.local"} - - tmpl := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{Organization: []string{organization}}, - SignatureAlgorithm: x509.SHA256WithRSA, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(1, 0, 0), // valid for 1 years - BasicConstraintsValid: true, - DNSNames: serviceNames, - } - return &tmpl, nil -} - -// Create cert template suitable for CA and hence signing -func createCACertTemplate(name, namespace string) (*x509.Certificate, error) { - rootCert, err := createCertTemplate(name, namespace) - if err != nil { - return nil, err - } - // Make it into a CA cert and change it so we can use it to sign certs - rootCert.IsCA = true - rootCert.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature - rootCert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} - return rootCert, nil -} - -// Create cert template that we can use on the server for TLS -func createServerCertTemplate(name, namespace string) (*x509.Certificate, error) { - serverCert, err := createCertTemplate(name, namespace) - if err != nil { - return nil, err - } - serverCert.KeyUsage = x509.KeyUsageDigitalSignature - serverCert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} - return serverCert, err -} - -// Actually sign the cert and return things in a form that we can use later on -func createCert(template, parent *x509.Certificate, pub interface{}, parentPriv interface{}) ( - cert *x509.Certificate, certPEM []byte, err error) { - - certDER, err := x509.CreateCertificate(rand.Reader, template, parent, pub, parentPriv) - if err != nil { - return - } - cert, err = x509.ParseCertificate(certDER) - if err != nil { - return - } - b := pem.Block{Type: "CERTIFICATE", Bytes: certDER} - certPEM = pem.EncodeToMemory(&b) - return -} - -func createCA(ctx context.Context, name, namespace string) (*rsa.PrivateKey, *x509.Certificate, []byte, error) { - logger := logging.FromContext(ctx) - rootKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - logger.Error("error generating random key", zap.Error(err)) - return nil, nil, nil, err - } - - rootCertTmpl, err := createCACertTemplate(name, namespace) - if err != nil { - logger.Error("error generating CA cert", zap.Error(err)) - return nil, nil, nil, err - } - - rootCert, rootCertPEM, err := createCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey) - if err != nil { - logger.Error("error signing the CA cert", zap.Error(err)) - return nil, nil, nil, err - } - return rootKey, rootCert, rootCertPEM, nil -} - -// CreateCerts creates and returns a CA certificate and certificate and -// key for the server. serverKey and serverCert are used by the server -// to establish trust for clients, CA certificate is used by the -// client to verify the server authentication chain. -func CreateCerts(ctx context.Context, name, namespace string) (serverKey, serverCert, caCert []byte, err error) { - logger := logging.FromContext(ctx) - // First create a CA certificate and private key - caKey, caCertificate, caCertificatePEM, err := createCA(ctx, name, namespace) - if err != nil { - return nil, nil, nil, err - } - - // Then create the private key for the serving cert - servKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - logger.Error("error generating random key", zap.Error(err)) - return nil, nil, nil, err - } - servCertTemplate, err := createServerCertTemplate(name, namespace) - if err != nil { - logger.Error("failed to create the server certificate template", zap.Error(err)) - return nil, nil, nil, err - } - - // create a certificate which wraps the server's public key, sign it with the CA private key - _, servCertPEM, err := createCert(servCertTemplate, caCertificate, &servKey.PublicKey, caKey) - if err != nil { - logger.Error("error signing server certificate template", zap.Error(err)) - return nil, nil, nil, err - } - servKeyPEM := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(servKey), - }) - return servKeyPEM, servCertPEM, caCertificatePEM, nil -} diff --git a/vendor/github.com/knative/pkg/webhook/webhook.go b/vendor/github.com/knative/pkg/webhook/webhook.go deleted file mode 100644 index 2b1da8d95dd..00000000000 --- a/vendor/github.com/knative/pkg/webhook/webhook.go +++ /dev/null @@ -1,646 +0,0 @@ -/* -Copyright 2017 The Knative 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 webhook - -import ( - "bytes" - "context" - "crypto/tls" - "crypto/x509" - "encoding/json" - "errors" - "fmt" - "net/http" - "reflect" - "sort" - "strings" - "time" - - "go.uber.org/zap" - - "github.com/knative/pkg/apis" - "github.com/knative/pkg/logging" - "github.com/knative/pkg/logging/logkey" - - "github.com/mattbaird/jsonpatch" - admissionv1beta1 "k8s.io/api/admission/v1beta1" - admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" - corev1 "k8s.io/api/core/v1" - v1beta1 "k8s.io/api/extensions/v1beta1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/kubernetes" - clientadmissionregistrationv1beta1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1" -) - -const ( - secretServerKey = "server-key.pem" - secretServerCert = "server-cert.pem" - secretCACert = "ca-cert.pem" -) - -var ( - deploymentKind = v1beta1.SchemeGroupVersion.WithKind("Deployment") - errMissingNewObject = errors.New("the new object may not be nil") -) - -// ControllerOptions contains the configuration for the webhook -type ControllerOptions struct { - // WebhookName is the name of the webhook we create to handle - // mutations before they get stored in the storage. - WebhookName string - - // ServiceName is the service name of the webhook. - ServiceName string - - // DeploymentName is the service name of the webhook. - DeploymentName string - - // SecretName is the name of k8s secret that contains the webhook - // server key/cert and corresponding CA cert that signed them. The - // server key/cert are used to serve the webhook and the CA cert - // is provided to k8s apiserver during admission controller - // registration. - SecretName string - - // Namespace is the namespace in which everything above lives - Namespace string - - // Port where the webhook is served. Per k8s admission - // registration requirements this should be 443 unless there is - // only a single port for the service. - Port int - - // RegistrationDelay controls how long admission registration - // occurs after the webhook is started. This is used to avoid - // potential races where registration completes and k8s apiserver - // invokes the webhook before the HTTP server is started. - RegistrationDelay time.Duration -} - -// ResourceCallback defines a signature for resource specific (Route, Configuration, etc.) -// handlers that can validate and mutate an object. If non-nil error is returned, object creation -// is denied. Mutations should be appended to the patches operations. -type ResourceCallback func(patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error - -// ResourceDefaulter defines a signature for resource specific (Route, Configuration, etc.) -// handlers that can set defaults on an object. If non-nil error is returned, object creation -// is denied. Mutations should be appended to the patches operations. -type ResourceDefaulter func(patches *[]jsonpatch.JsonPatchOperation, crd GenericCRD) error - -// AdmissionController implements the external admission webhook for validation of -// pilot configuration. -type AdmissionController struct { - Client kubernetes.Interface - Options ControllerOptions - GroupVersion schema.GroupVersion - Handlers map[string]runtime.Object - Logger *zap.SugaredLogger -} - -// GenericCRD is the interface definition that allows us to perform the generic -// CRD actions like deciding whether to increment generation and so forth. -type GenericCRD interface { - apis.Defaultable - apis.Validatable - - // GetObjectMeta return the object metadata - GetObjectMeta() metav1.Object - // GetGeneration returns the current Generation of the object - GetGeneration() int64 - // SetGeneration sets the Generation of the object - SetGeneration(int64) - // GetSpecJSON returns the Spec part of the resource marshalled into JSON - GetSpecJSON() ([]byte, error) -} - -// GetAPIServerExtensionCACert gets the Kubernetes aggregate apiserver -// client CA cert used by validator. -// -// NOTE: this certificate is provided kubernetes. We do not control -// its name or location. -func getAPIServerExtensionCACert(cl kubernetes.Interface) ([]byte, error) { - const name = "extension-apiserver-authentication" - c, err := cl.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - pem, ok := c.Data["requestheader-client-ca-file"] - if !ok { - return nil, fmt.Errorf("cannot find ca.crt in %v: ConfigMap.Data is %#v", name, c.Data) - } - return []byte(pem), nil -} - -// MakeTLSConfig makes a TLS configuration suitable for use with the server -func makeTLSConfig(serverCert, serverKey, caCert []byte) (*tls.Config, error) { - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(caCert) - cert, err := tls.X509KeyPair(serverCert, serverKey) - if err != nil { - return nil, err - } - return &tls.Config{ - Certificates: []tls.Certificate{cert}, - ClientCAs: caCertPool, - ClientAuth: tls.NoClientCert, - // Note on GKE there apparently is no client cert sent, so this - // does not work on GKE. - // TODO: make this into a configuration option. - // ClientAuth: tls.RequireAndVerifyClientCert, - }, nil -} - -func getOrGenerateKeyCertsFromSecret(ctx context.Context, client kubernetes.Interface, - options *ControllerOptions) (serverKey, serverCert, caCert []byte, err error) { - logger := logging.FromContext(ctx) - secret, err := client.CoreV1().Secrets(options.Namespace).Get(options.SecretName, metav1.GetOptions{}) - if err != nil { - if !apierrors.IsNotFound(err) { - return nil, nil, nil, err - } - logger.Info("Did not find existing secret, creating one") - newSecret, err := generateSecret(ctx, options) - if err != nil { - return nil, nil, nil, err - } - secret, err = client.CoreV1().Secrets(newSecret.Namespace).Create(newSecret) - if err != nil && !apierrors.IsAlreadyExists(err) { - return nil, nil, nil, err - } - // Ok, so something else might have created, try fetching it one more time - secret, err = client.CoreV1().Secrets(options.Namespace).Get(options.SecretName, metav1.GetOptions{}) - if err != nil { - return nil, nil, nil, err - } - } - - var ok bool - if serverKey, ok = secret.Data[secretServerKey]; !ok { - return nil, nil, nil, errors.New("server key missing") - } - if serverCert, ok = secret.Data[secretServerCert]; !ok { - return nil, nil, nil, errors.New("server cert missing") - } - if caCert, ok = secret.Data[secretCACert]; !ok { - return nil, nil, nil, errors.New("ca cert missing") - } - return serverKey, serverCert, caCert, nil -} - -// Validate checks whether "new" and "old" implement HasImmutableFields and checks them, -// it then delegates validation to apis.Validatable on "new". -func Validate(ctx context.Context) ResourceCallback { - return func(patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error { - if hifNew, ok := new.(apis.Immutable); ok && old != nil { - hifOld, ok := old.(apis.Immutable) - if !ok { - return fmt.Errorf("unexpected type mismatch %T vs. %T", old, new) - } - if err := hifNew.CheckImmutableFields(hifOld); err != nil { - return err - } - } - // Can't just `return new.Validate()` because it doesn't properly nil-check. - if err := new.Validate(); err != nil { - return err - } - return nil - } -} - -// SetDefaults simply leverages apis.Defaultable to set defaults. -func SetDefaults(ctx context.Context) ResourceDefaulter { - return func(patches *[]jsonpatch.JsonPatchOperation, crd GenericCRD) error { - rawOriginal, err := json.Marshal(crd) - if err != nil { - return err - } - crd.SetDefaults() - - // Marshal the before and after. - rawAfter, err := json.Marshal(crd) - if err != nil { - return err - } - - patch, err := jsonpatch.CreatePatch(rawOriginal, rawAfter) - if err != nil { - return err - } - *patches = append(*patches, patch...) - return nil - } -} - -func configureCerts(ctx context.Context, client kubernetes.Interface, options *ControllerOptions) (*tls.Config, []byte, error) { - apiServerCACert, err := getAPIServerExtensionCACert(client) - if err != nil { - return nil, nil, err - } - serverKey, serverCert, caCert, err := getOrGenerateKeyCertsFromSecret( - ctx, client, options) - if err != nil { - return nil, nil, err - } - tlsConfig, err := makeTLSConfig(serverCert, serverKey, apiServerCACert) - if err != nil { - return nil, nil, err - } - return tlsConfig, caCert, nil -} - -// Run implements the admission controller run loop. -func (ac *AdmissionController) Run(stop <-chan struct{}) error { - logger := ac.Logger - ctx := logging.WithLogger(context.TODO(), logger) - tlsConfig, caCert, err := configureCerts(ctx, ac.Client, &ac.Options) - if err != nil { - logger.Error("Could not configure admission webhook certs", zap.Error(err)) - return err - } - - server := &http.Server{ - Handler: ac, - Addr: fmt.Sprintf(":%v", ac.Options.Port), - TLSConfig: tlsConfig, - } - - logger.Info("Found certificates for webhook...") - if ac.Options.RegistrationDelay != 0 { - logger.Infof("Delaying admission webhook registration for %v", ac.Options.RegistrationDelay) - } - - select { - case <-time.After(ac.Options.RegistrationDelay): - cl := ac.Client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations() - if err := ac.register(ctx, cl, caCert); err != nil { - logger.Error("Failed to register webhook", zap.Error(err)) - return err - } - defer func() { - if err := ac.unregister(ctx, cl); err != nil { - logger.Error("Failed to unregister webhook", zap.Error(err)) - } - }() - logger.Info("Successfully registered webhook") - case <-stop: - return nil - } - - go func() { - if err := server.ListenAndServeTLS("", ""); err != nil { - logger.Error("ListenAndServeTLS for admission webhook returned error", zap.Error(err)) - } - }() - <-stop - server.Close() // nolint: errcheck - return nil -} - -// Unregister unregisters the external admission webhook -func (ac *AdmissionController) unregister( - ctx context.Context, client clientadmissionregistrationv1beta1.MutatingWebhookConfigurationInterface) error { - logger := logging.FromContext(ctx) - logger.Info("Exiting..") - return nil -} - -// Register registers the external admission webhook for pilot -// configuration types. -func (ac *AdmissionController) register( - ctx context.Context, client clientadmissionregistrationv1beta1.MutatingWebhookConfigurationInterface, caCert []byte) error { // nolint: lll - logger := logging.FromContext(ctx) - failurePolicy := admissionregistrationv1beta1.Fail - - resources := sort.StringSlice{} - for k := range ac.Handlers { - // Lousy pluralizer - resources = append(resources, strings.ToLower(k)+"s") - } - resources.Sort() - - webhook := &admissionregistrationv1beta1.MutatingWebhookConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: ac.Options.WebhookName, - }, - Webhooks: []admissionregistrationv1beta1.Webhook{{ - Name: ac.Options.WebhookName, - Rules: []admissionregistrationv1beta1.RuleWithOperations{{ - Operations: []admissionregistrationv1beta1.OperationType{ - admissionregistrationv1beta1.Create, - admissionregistrationv1beta1.Update, - }, - Rule: admissionregistrationv1beta1.Rule{ - APIGroups: []string{ac.GroupVersion.Group}, - APIVersions: []string{ac.GroupVersion.Version}, - Resources: resources, - }, - }}, - ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{ - Service: &admissionregistrationv1beta1.ServiceReference{ - Namespace: ac.Options.Namespace, - Name: ac.Options.ServiceName, - }, - CABundle: caCert, - }, - FailurePolicy: &failurePolicy, - }}, - } - - // Set the owner to our deployment - deployment, err := ac.Client.ExtensionsV1beta1().Deployments(ac.Options.Namespace).Get(ac.Options.DeploymentName, metav1.GetOptions{}) - if err != nil { - return fmt.Errorf("Failed to fetch our deployment: %s", err) - } - deploymentRef := metav1.NewControllerRef(deployment, deploymentKind) - webhook.OwnerReferences = append(webhook.OwnerReferences, *deploymentRef) - - // Try to create the webhook and if it already exists validate webhook rules - _, err = client.Create(webhook) - if err != nil { - if !apierrors.IsAlreadyExists(err) { - return fmt.Errorf("Failed to create a webhook: %s", err) - } - logger.Info("Webhook already exists") - configuredWebhook, err := client.Get(ac.Options.WebhookName, metav1.GetOptions{}) - if err != nil { - return fmt.Errorf("Error retrieving webhook: %s", err) - } - if !reflect.DeepEqual(configuredWebhook.Webhooks, webhook.Webhooks) { - logger.Info("Updating webhook") - // Set the ResourceVersion as required by update. - webhook.ObjectMeta.ResourceVersion = configuredWebhook.ObjectMeta.ResourceVersion - if _, err := client.Update(webhook); err != nil { - return fmt.Errorf("Failed to update webhook: %s", err) - } - } else { - logger.Info("Webhook is already valid") - } - } else { - logger.Info("Created a webhook") - } - return nil -} - -// ServeHTTP implements the external admission webhook for mutating -// serving resources. -func (ac *AdmissionController) ServeHTTP(w http.ResponseWriter, r *http.Request) { - logger := ac.Logger - logger.Infof("Webhook ServeHTTP request=%#v", r) - - // verify the content type is accurate - contentType := r.Header.Get("Content-Type") - if contentType != "application/json" { - http.Error(w, "invalid Content-Type, want `application/json`", http.StatusUnsupportedMediaType) - return - } - - var review admissionv1beta1.AdmissionReview - defer r.Body.Close() - if err := json.NewDecoder(r.Body).Decode(&review); err != nil { - http.Error(w, fmt.Sprintf("could not decode body: %v", err), http.StatusBadRequest) - return - } - - logger = logger.With( - zap.String(logkey.Kind, fmt.Sprint(review.Request.Kind)), - zap.String(logkey.Namespace, review.Request.Namespace), - zap.String(logkey.Name, review.Request.Name), - zap.String(logkey.Operation, fmt.Sprint(review.Request.Operation)), - zap.String(logkey.Resource, fmt.Sprint(review.Request.Resource)), - zap.String(logkey.SubResource, fmt.Sprint(review.Request.SubResource)), - zap.String(logkey.UserInfo, fmt.Sprint(review.Request.UserInfo))) - reviewResponse := ac.admit(logging.WithLogger(r.Context(), logger), review.Request) - var response admissionv1beta1.AdmissionReview - if reviewResponse != nil { - response.Response = reviewResponse - response.Response.UID = review.Request.UID - } - - logger.Infof("AdmissionReview for %s: %v/%v response=%v", - review.Request.Kind, review.Request.Namespace, review.Request.Name, reviewResponse) - - if err := json.NewEncoder(w).Encode(response); err != nil { - http.Error(w, fmt.Sprintf("could encode response: %v", err), http.StatusInternalServerError) - return - } -} - -func makeErrorStatus(reason string, args ...interface{}) *admissionv1beta1.AdmissionResponse { - result := apierrors.NewBadRequest(fmt.Sprintf(reason, args...)).Status() - return &admissionv1beta1.AdmissionResponse{ - Result: &result, - Allowed: false, - } -} - -func (ac *AdmissionController) admit(ctx context.Context, request *admissionv1beta1.AdmissionRequest) *admissionv1beta1.AdmissionResponse { - logger := logging.FromContext(ctx) - switch request.Operation { - case admissionv1beta1.Create, admissionv1beta1.Update: - default: - logger.Infof("Unhandled webhook operation, letting it through %v", request.Operation) - return &admissionv1beta1.AdmissionResponse{Allowed: true} - } - - patchBytes, err := ac.mutate(ctx, request.Kind.Kind, request.OldObject.Raw, request.Object.Raw) - if err != nil { - return makeErrorStatus("mutation failed: %v", err) - } - logger.Infof("Kind: %q PatchBytes: %v", request.Kind, string(patchBytes)) - - return &admissionv1beta1.AdmissionResponse{ - Patch: patchBytes, - Allowed: true, - PatchType: func() *admissionv1beta1.PatchType { - pt := admissionv1beta1.PatchTypeJSONPatch - return &pt - }(), - } -} - -func (ac *AdmissionController) mutate(ctx context.Context, kind string, oldBytes []byte, newBytes []byte) ([]byte, error) { - logger := logging.FromContext(ctx) - handler, ok := ac.Handlers[kind] - if !ok { - logger.Errorf("Unhandled kind %q", kind) - return nil, fmt.Errorf("unhandled kind: %q", kind) - } - - oldObj := handler.DeepCopyObject().(GenericCRD) - newObj := handler.DeepCopyObject().(GenericCRD) - - if len(newBytes) != 0 { - newDecoder := json.NewDecoder(bytes.NewBuffer(newBytes)) - newDecoder.DisallowUnknownFields() - if err := newDecoder.Decode(&newObj); err != nil { - return nil, fmt.Errorf("cannot decode incoming new object: %v", err) - } - } else { - // Use nil to denote the absence of a new object (delete) - newObj = nil - } - - if len(oldBytes) != 0 { - oldDecoder := json.NewDecoder(bytes.NewBuffer(oldBytes)) - oldDecoder.DisallowUnknownFields() - if err := oldDecoder.Decode(&oldObj); err != nil { - return nil, fmt.Errorf("cannot decode incoming old object: %v", err) - } - } else { - // Use nil to denote the absence of an old object (create) - oldObj = nil - } - - var patches []jsonpatch.JsonPatchOperation - - err := updateGeneration(ctx, &patches, oldObj, newObj) - if err != nil { - logger.Error("Failed to update generation", zap.Error(err)) - return nil, fmt.Errorf("Failed to update generation: %s", err) - } - - if defaulter := SetDefaults(ctx); defaulter != nil { - if err := defaulter(&patches, newObj); err != nil { - logger.Error("Failed the resource specific defaulter", zap.Error(err)) - // Return the error message as-is to give the defaulter callback - // discretion over (our portion of) the message that the user sees. - return nil, err - } - } - - // None of the validators will accept a nil value for newObj. - if newObj == nil { - return nil, errMissingNewObject - } - if validator := Validate(ctx); validator != nil { - if err := validator(&patches, oldObj, newObj); err != nil { - logger.Error("Failed the resource specific validation", zap.Error(err)) - // Return the error message as-is to give the validation callback - // discretion over (our portion of) the message that the user sees. - return nil, err - } - } - - if err := validateMetadata(newObj); err != nil { - logger.Error("Failed to validate", zap.Error(err)) - return nil, fmt.Errorf("Failed to validate: %s", err) - } - return json.Marshal(patches) -} - -func validateMetadata(new GenericCRD) error { - name := new.GetObjectMeta().GetName() - - if strings.Contains(name, ".") { - return errors.New("Invalid resource name: special character . must not be present") - } - - if len(name) > 63 { - return errors.New("Invalid resource name: length must be no more than 63 characters") - } - return nil -} - -// updateGeneration sets the generation by following this logic: -// if there's no old object, it's create, set generation to 1 -// if there's an old object and spec has changed, set generation to oldGeneration + 1 -// appends the patch to patches if changes are necessary. -// TODO: Generation does not work correctly with CRD. They are scrubbed -// by the APIserver (https://github.com/kubernetes/kubernetes/issues/58778) -// So, we add Generation here. Once that gets fixed, remove this and use -// ObjectMeta.Generation instead. -func updateGeneration(ctx context.Context, patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error { - logger := logging.FromContext(ctx) - var oldGeneration int64 - if old == nil { - logger.Info("Old is nil") - } else { - oldGeneration = old.GetGeneration() - } - if oldGeneration == 0 { - logger.Info("Creating an object, setting generation to 1") - *patches = append(*patches, jsonpatch.JsonPatchOperation{ - Operation: "add", - Path: "/spec/generation", - Value: 1, - }) - return nil - } - - oldSpecJSON, err := old.GetSpecJSON() - if err != nil { - logger.Error("Failed to get Spec JSON for old", zap.Error(err)) - } - newSpecJSON, err := new.GetSpecJSON() - if err != nil { - logger.Error("Failed to get Spec JSON for new", zap.Error(err)) - } - - specPatches, err := jsonpatch.CreatePatch(oldSpecJSON, newSpecJSON) - if err != nil { - fmt.Printf("Error creating JSON patch:%v", err) - return err - } - if len(specPatches) > 0 { - specPatchesJSON, err := json.Marshal(specPatches) - if err != nil { - logger.Error("Failed to marshal spec patches", zap.Error(err)) - return err - } - logger.Infof("Specs differ:\n%+v\n", string(specPatchesJSON)) - - operation := "replace" - if newGeneration := new.GetGeneration(); newGeneration == 0 { - // If new is missing Generation, we need to "add" instead of "replace". - // We see this for Service resources because the initial generation is - // added to the managed Configuration and Route, but not the Service - // that manages them. - // TODO(#642): Remove this. - operation = "add" - } - *patches = append(*patches, jsonpatch.JsonPatchOperation{ - Operation: operation, - Path: "/spec/generation", - Value: oldGeneration + 1, - }) - return nil - } - logger.Info("No changes in the spec, not bumping generation") - return nil -} - -func generateSecret(ctx context.Context, options *ControllerOptions) (*corev1.Secret, error) { - serverKey, serverCert, caCert, err := CreateCerts(ctx, options.ServiceName, options.Namespace) - if err != nil { - return nil, err - } - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: options.SecretName, - Namespace: options.Namespace, - }, - Data: map[string][]byte{ - secretServerKey: serverKey, - secretServerCert: serverCert, - secretCACert: caCert, - }, - }, nil -} From 5ecf818518ac711444de16c19c4595b0de0c0894 Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Wed, 1 Aug 2018 13:06:40 -0700 Subject: [PATCH 12/13] Don't dep on glog in cmd. --- cmd/controller/main.go | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 3e8f0f9454c..718a6a5fbdf 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -22,7 +22,6 @@ import ( "net/http" "time" - "github.com/golang/glog" kubeinformers "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -85,22 +84,22 @@ func main() { cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig) if err != nil { - glog.Fatalf("Error building kubeconfig: %s", err.Error()) + logger.Fatalf("Error building kubeconfig: %s", err.Error()) } kubeClient, err := kubernetes.NewForConfig(cfg) if err != nil { - glog.Fatalf("Error building kubernetes clientset: %s", err.Error()) + logger.Fatalf("Error building kubernetes clientset: %s", err.Error()) } client, err := clientset.NewForConfig(cfg) if err != nil { - glog.Fatalf("Error building clientset: %s", err.Error()) + logger.Fatalf("Error building clientset: %s", err.Error()) } servingClient, err := servingclientset.NewForConfig(cfg) if err != nil { - glog.Fatalf("Error building serving clientset: %s", err.Error()) + logger.Fatalf("Error building serving clientset: %s", err.Error()) } // TODO: Rip this out from all the controllers since we can get it @@ -109,7 +108,7 @@ func main() { // Kubernetes. Clients will use the Pod's ServiceAccount principal. restConfig, err := rest.InClusterConfig() if err != nil { - glog.Fatalf("Error building rest config: %v", err.Error()) + logger.Fatalf("Error building rest config: %v", err.Error()) } kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, time.Second*30) @@ -148,7 +147,7 @@ func main() { // We don't expect this to return until stop is called, // but if it does, propagate it back. if err := ctrlr.Run(threadsPerController, stopCh); err != nil { - glog.Fatalf("Error running controller: %s", err.Error()) + logger.Fatalf("Error running controller: %s", err.Error()) } }(ctrlr) } @@ -157,9 +156,9 @@ func main() { srv := &http.Server{Addr: metricsScrapeAddr} http.Handle(metricsScrapePath, promhttp.Handler()) go func() { - glog.Info("Starting metrics listener at %s", metricsScrapeAddr) + logger.Info("Starting metrics listener at %s", metricsScrapeAddr) if err := srv.ListenAndServe(); err != nil { - glog.Infof("Httpserver: ListenAndServe() finished with error: %s", err) + logger.Infof("Httpserver: ListenAndServe() finished with error: %s", err) } }() @@ -169,8 +168,6 @@ func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() srv.Shutdown(ctx) - - glog.Flush() } func init() { From 2336606356941c6d8bb1bcd4b6436341f4ec0804 Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Thu, 2 Aug 2018 09:23:03 -0700 Subject: [PATCH 13/13] Cleanup based on review. --- cmd/controller-manager/main.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/controller-manager/main.go b/cmd/controller-manager/main.go index d586dd99028..4a55e57af08 100644 --- a/cmd/controller-manager/main.go +++ b/cmd/controller-manager/main.go @@ -41,7 +41,7 @@ import ( "k8s.io/client-go/rest" "github.com/knative/eventing/pkg/system" - clientconfig "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/manager" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" @@ -62,16 +62,17 @@ func main() { if err != nil { log.Fatalf("Error loading logging configuration: %v", err) } - config, err := logging.NewConfigFromMap(cm, logconfig.ControllerManager) + cfg, err := logging.NewConfigFromMap(cm, logconfig.ControllerManager) if err != nil { log.Fatalf("Error parsing logging configuration: %v", err) } - logger, atomicLevel := logging.NewLoggerFromConfig(config, logconfig.ControllerManager) + logger, atomicLevel := logging.NewLoggerFromConfig(cfg, logconfig.ControllerManager) defer logger.Sync() logger = logger.With(zap.String(logkey.ControllerType, logconfig.ControllerManager)) logger.Info("Starting the Controller Manager") + // This tells controller-runtime to use zap to log internal messages. logf.SetLogger(logf.ZapLogger(false)) // set up signals so we handle the first shutdown signal gracefully @@ -95,7 +96,7 @@ func main() { } // Setup a Manager - mrg, err := manager.New(clientconfig.GetConfigOrDie(), manager.Options{}) + mrg, err := manager.New(config.GetConfigOrDie(), manager.Options{}) if err != nil { log.Fatal(err) }