Update go dependencies

This commit is contained in:
Manuel Alejandro de Brito Fontes 2019-05-22 18:19:08 -04:00
parent c639f490b1
commit d3c957192e
No known key found for this signature in database
GPG key ID: 786136016A8BA02A
125 changed files with 12284 additions and 0 deletions

View file

@ -0,0 +1,95 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"flag"
"fmt"
"os"
"os/user"
"path/filepath"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
)
var (
kubeconfig, masterURL string
log = logf.KBLog.WithName("client").WithName("config")
)
func init() {
// TODO: Fix this to allow double vendoring this library but still register flags on behalf of users
flag.StringVar(&kubeconfig, "kubeconfig", "",
"Paths to a kubeconfig. Only required if out-of-cluster.")
flag.StringVar(&masterURL, "master", "",
"The address of the Kubernetes API server. Overrides any value in kubeconfig. "+
"Only required if out-of-cluster.")
}
// GetConfig creates a *rest.Config for talking to a Kubernetes apiserver.
// If --kubeconfig is set, will use the kubeconfig file at that location. Otherwise will assume running
// in cluster and use the cluster provided kubeconfig.
//
// Config precedence
//
// * --kubeconfig flag pointing at a file
//
// * KUBECONFIG environment variable pointing at a file
//
// * In-cluster config if running in cluster
//
// * $HOME/.kube/config if exists
func GetConfig() (*rest.Config, error) {
// If a flag is specified with the config location, use that
if len(kubeconfig) > 0 {
return clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
}
// If an env variable is specified with the config locaiton, use that
if len(os.Getenv("KUBECONFIG")) > 0 {
return clientcmd.BuildConfigFromFlags(masterURL, os.Getenv("KUBECONFIG"))
}
// If no explicit location, try the in-cluster config
if c, err := rest.InClusterConfig(); err == nil {
return c, nil
}
// If no in-cluster config, try the default location in the user's home directory
if usr, err := user.Current(); err == nil {
if c, err := clientcmd.BuildConfigFromFlags(
"", filepath.Join(usr.HomeDir, ".kube", "config")); err == nil {
return c, nil
}
}
return nil, fmt.Errorf("could not locate a kubeconfig")
}
// GetConfigOrDie creates a *rest.Config for talking to a Kubernetes apiserver.
// If --kubeconfig is set, will use the kubeconfig file at that location. Otherwise will assume running
// in cluster and use the cluster provided kubeconfig.
//
// Will log an error and exit if there is an error creating the rest.Config.
func GetConfigOrDie() *rest.Config {
config, err := GetConfig()
if err != nil {
log.Error(err, "unable to get kubeconfig")
os.Exit(1)
}
return config
}

View file

@ -0,0 +1,18 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package config contains libraries for initializing rest configs for talking to the Kubernetes API
package config

View file

@ -0,0 +1,224 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package envtest
import (
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/ghodss/yaml"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/rest"
)
// CRDInstallOptions are the options for installing CRDs
type CRDInstallOptions struct {
// Paths is the path to the directory containing CRDs
Paths []string
// CRDs is a list of CRDs to install
CRDs []*apiextensionsv1beta1.CustomResourceDefinition
// ErrorIfPathMissing will cause an error if a Path does not exist
ErrorIfPathMissing bool
// maxTime is the max time to wait
maxTime time.Duration
// pollInterval is the interval to check
pollInterval time.Duration
}
const defaultPollInterval = 100 * time.Millisecond
const defaultMaxWait = 10 * time.Second
// InstallCRDs installs a collection of CRDs into a cluster by reading the crd yaml files from a directory
func InstallCRDs(config *rest.Config, options CRDInstallOptions) ([]*apiextensionsv1beta1.CustomResourceDefinition, error) {
defaultCRDOptions(&options)
// Read the CRD yamls into options.CRDs
if err := readCRDFiles(&options); err != nil {
return nil, err
}
// Create the CRDs in the apiserver
if err := CreateCRDs(config, options.CRDs); err != nil {
return options.CRDs, err
}
// Wait for the CRDs to appear as Resources in the apiserver
if err := WaitForCRDs(config, options.CRDs, options); err != nil {
return options.CRDs, err
}
return options.CRDs, nil
}
// readCRDFiles reads the directories of CRDs in options.Paths and adds the CRD structs to options.CRDs
func readCRDFiles(options *CRDInstallOptions) error {
if len(options.Paths) > 0 {
for _, path := range options.Paths {
if _, err := os.Stat(path); !options.ErrorIfPathMissing && os.IsNotExist(err) {
continue
}
new, err := readCRDs(path)
if err != nil {
return err
}
options.CRDs = append(options.CRDs, new...)
}
}
return nil
}
// defaultCRDOptions sets the default values for CRDs
func defaultCRDOptions(o *CRDInstallOptions) {
if o.maxTime == 0 {
o.maxTime = defaultMaxWait
}
if o.pollInterval == 0 {
o.pollInterval = defaultPollInterval
}
}
// WaitForCRDs waits for the CRDs to appear in discovery
func WaitForCRDs(config *rest.Config, crds []*apiextensionsv1beta1.CustomResourceDefinition, options CRDInstallOptions) error {
// Add each CRD to a map of GroupVersion to Resource
waitingFor := map[schema.GroupVersion]*sets.String{}
for _, crd := range crds {
gv := schema.GroupVersion{Group: crd.Spec.Group, Version: crd.Spec.Version}
if _, found := waitingFor[gv]; !found {
// Initialize the set
waitingFor[gv] = &sets.String{}
}
// Add the Resource
waitingFor[gv].Insert(crd.Spec.Names.Plural)
}
// Poll until all resources are found in discovery
p := &poller{config: config, waitingFor: waitingFor}
return wait.PollImmediate(options.pollInterval, options.maxTime, p.poll)
}
// poller checks if all the resources have been found in discovery, and returns false if not
type poller struct {
// config is used to get discovery
config *rest.Config
// waitingFor is the map of resources keyed by group version that have not yet been found in discovery
waitingFor map[schema.GroupVersion]*sets.String
}
// poll checks if all the resources have been found in discovery, and returns false if not
func (p *poller) poll() (done bool, err error) {
// Create a new clientset to avoid any client caching of discovery
cs, err := clientset.NewForConfig(p.config)
if err != nil {
return false, err
}
allFound := true
for gv, resources := range p.waitingFor {
// All resources found, do nothing
if resources.Len() == 0 {
delete(p.waitingFor, gv)
continue
}
// Get the Resources for this GroupVersion
// TODO: Maybe the controller-runtime client should be able to do this...
resourceList, err := cs.Discovery().ServerResourcesForGroupVersion(gv.Group + "/" + gv.Version)
if err != nil {
return false, nil
}
// Remove each found resource from the resources set that we are waiting for
for _, resource := range resourceList.APIResources {
resources.Delete(resource.Name)
}
// Still waiting on some resources in this group version
if resources.Len() != 0 {
allFound = false
}
}
return allFound, nil
}
// CreateCRDs creates the CRDs
func CreateCRDs(config *rest.Config, crds []*apiextensionsv1beta1.CustomResourceDefinition) error {
cs, err := clientset.NewForConfig(config)
if err != nil {
return err
}
// Create each CRD
for _, crd := range crds {
log.V(1).Info("installing CRD", "crd", crd)
if _, err := cs.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd); err != nil {
return err
}
}
return nil
}
// readCRDs reads the CRDs from files and Unmarshals them into structs
func readCRDs(path string) ([]*apiextensionsv1beta1.CustomResourceDefinition, error) {
// Get the CRD files
var files []os.FileInfo
var err error
log.V(1).Info("reading CRDs from path", "path", path)
if files, err = ioutil.ReadDir(path); err != nil {
return nil, err
}
// White list the file extensions that may contain CRDs
crdExts := sets.NewString(".json", ".yaml", ".yml")
var crds []*apiextensionsv1beta1.CustomResourceDefinition
for _, file := range files {
// Only parse whitelisted file types
if !crdExts.Has(filepath.Ext(file.Name())) {
continue
}
// Unmarshal the file into a struct
b, err := ioutil.ReadFile(filepath.Join(path, file.Name()))
if err != nil {
return nil, err
}
crd := &apiextensionsv1beta1.CustomResourceDefinition{}
if err = yaml.Unmarshal(b, crd); err != nil {
return nil, err
}
// Check that it is actually a CRD
if crd.Spec.Names.Kind == "" || crd.Spec.Group == "" {
continue
}
log.V(1).Info("read CRD from file", "file", file)
crds = append(crds, crd)
}
return crds, nil
}

View file

@ -0,0 +1,18 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package envtest provides libraries for integration testing by starting a local control plane
package envtest

View file

@ -0,0 +1,11 @@
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: foos.bar.example.com
spec:
group: bar.example.com
names:
kind: Foo
plural: foos
scope: Namespaced
version: "v1beta1"

View file

@ -0,0 +1,11 @@
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: bazs.qux.example.com
spec:
group: qux.example.com
names:
kind: Baz
plural: bazs
scope: Namespaced
version: "v1beta1"

View file

@ -0,0 +1,11 @@
package envtest
import (
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
)
// NewlineReporter is Reporter that Prints a newline after the default Reporter output so that the results
// are correctly parsed by test automation.
// See issue https://github.com/jstemmer/go-junit-report/issues/31
// It's re-exported here to avoid compatibility breakage/mass rewrites.
type NewlineReporter = printer.NewlineReporter

View file

@ -0,0 +1,18 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9

View file

@ -0,0 +1,51 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package printer
import (
"fmt"
"github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/types"
)
var _ ginkgo.Reporter = NewlineReporter{}
// NewlineReporter is Reporter that Prints a newline after the default Reporter output so that the results
// are correctly parsed by test automation.
// See issue https://github.com/jstemmer/go-junit-report/issues/31
type NewlineReporter struct{}
// SpecSuiteWillBegin implements ginkgo.Reporter
func (NewlineReporter) SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) {
}
// BeforeSuiteDidRun implements ginkgo.Reporter
func (NewlineReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {}
// AfterSuiteDidRun implements ginkgo.Reporter
func (NewlineReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {}
// SpecWillRun implements ginkgo.Reporter
func (NewlineReporter) SpecWillRun(specSummary *types.SpecSummary) {}
// SpecDidComplete implements ginkgo.Reporter
func (NewlineReporter) SpecDidComplete(specSummary *types.SpecSummary) {}
// SpecSuiteDidEnd Prints a newline between "35 Passed | 0 Failed | 0 Pending | 0 Skipped" and "--- PASS:"
func (NewlineReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) { fmt.Printf("\n") }

View file

@ -0,0 +1,222 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package envtest
import (
"fmt"
"os"
"path/filepath"
"time"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/testing_frameworks/integration"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
)
var log = logf.KBLog.WithName("test-env")
// Default binary path for test framework
const (
envKubeAPIServerBin = "TEST_ASSET_KUBE_APISERVER"
envEtcdBin = "TEST_ASSET_ETCD"
envKubectlBin = "TEST_ASSET_KUBECTL"
envKubebuilderPath = "KUBEBUILDER_ASSETS"
envStartTimeout = "KUBEBUILDER_CONTROLPLANE_START_TIMEOUT"
envStopTimeout = "KUBEBUILDER_CONTROLPLANE_STOP_TIMEOUT"
defaultKubebuilderPath = "/usr/local/kubebuilder/bin"
StartTimeout = 60
StopTimeout = 60
defaultKubebuilderControlPlaneStartTimeout = 20 * time.Second
defaultKubebuilderControlPlaneStopTimeout = 20 * time.Second
)
func defaultAssetPath(binary string) string {
assetPath := os.Getenv(envKubebuilderPath)
if assetPath == "" {
assetPath = defaultKubebuilderPath
}
return filepath.Join(assetPath, binary)
}
// DefaultKubeAPIServerFlags are default flags necessary to bring up apiserver.
var DefaultKubeAPIServerFlags = []string{
"--etcd-servers={{ if .EtcdURL }}{{ .EtcdURL.String }}{{ end }}",
"--cert-dir={{ .CertDir }}",
"--insecure-port={{ if .URL }}{{ .URL.Port }}{{ end }}",
"--insecure-bind-address={{ if .URL }}{{ .URL.Hostname }}{{ end }}",
"--secure-port={{ if .SecurePort }}{{ .SecurePort }}{{ end }}",
"--admission-control=AlwaysAdmit",
}
// Environment creates a Kubernetes test environment that will start / stop the Kubernetes control plane and
// install extension APIs
type Environment struct {
// ControlPlane is the ControlPlane including the apiserver and etcd
ControlPlane integration.ControlPlane
// Config can be used to talk to the apiserver
Config *rest.Config
// CRDs is a list of CRDs to install
CRDs []*apiextensionsv1beta1.CustomResourceDefinition
// CRDDirectoryPaths is a list of paths containing CRD yaml or json configs.
CRDDirectoryPaths []string
// UseExisting indicates that this environments should use an
// existing kubeconfig, instead of trying to stand up a new control plane.
// This is useful in cases that need aggregated API servers and the like.
UseExistingCluster bool
// ControlPlaneStartTimeout is the maximum duration each controlplane component
// may take to start. It defaults to the KUBEBUILDER_CONTROLPLANE_START_TIMEOUT
// environment variable or 20 seconds if unspecified
ControlPlaneStartTimeout time.Duration
// ControlPlaneStopTimeout is the maximum duration each controlplane component
// may take to stop. It defaults to the KUBEBUILDER_CONTROLPLANE_STOP_TIMEOUT
// environment variable or 20 seconds if unspecified
ControlPlaneStopTimeout time.Duration
// KubeAPIServerFlags is the set of flags passed while starting the api server.
KubeAPIServerFlags []string
}
// Stop stops a running server
func (te *Environment) Stop() error {
if te.UseExistingCluster {
return nil
}
return te.ControlPlane.Stop()
}
// getAPIServerFlags returns flags to be used with the Kubernetes API server.
func (te Environment) getAPIServerFlags() []string {
// Set default API server flags if not set.
if len(te.KubeAPIServerFlags) == 0 {
return DefaultKubeAPIServerFlags
}
return te.KubeAPIServerFlags
}
// Start starts a local Kubernetes server and updates te.ApiserverPort with the port it is listening on
func (te *Environment) Start() (*rest.Config, error) {
if te.UseExistingCluster {
log.V(1).Info("using existing cluster")
if te.Config == nil {
// we want to allow people to pass in their own config, so
// only load a config if it hasn't already been set.
log.V(1).Info("automatically acquiring client configuration")
var err error
te.Config, err = config.GetConfig()
if err != nil {
return nil, err
}
}
} else {
te.ControlPlane = integration.ControlPlane{}
te.ControlPlane.APIServer = &integration.APIServer{Args: te.getAPIServerFlags()}
te.ControlPlane.Etcd = &integration.Etcd{}
if os.Getenv(envKubeAPIServerBin) == "" {
te.ControlPlane.APIServer.Path = defaultAssetPath("kube-apiserver")
}
if os.Getenv(envEtcdBin) == "" {
te.ControlPlane.Etcd.Path = defaultAssetPath("etcd")
}
if os.Getenv(envKubectlBin) == "" {
// we can't just set the path manually (it's behind a function), so set the environment variable instead
if err := os.Setenv(envKubectlBin, defaultAssetPath("kubectl")); err != nil {
return nil, err
}
}
if err := te.defaultTimeouts(); err != nil {
return nil, fmt.Errorf("failed to default controlplane timeouts: %v", err)
}
te.ControlPlane.Etcd.StartTimeout = te.ControlPlaneStartTimeout
te.ControlPlane.Etcd.StopTimeout = te.ControlPlaneStopTimeout
te.ControlPlane.APIServer.StartTimeout = te.ControlPlaneStartTimeout
te.ControlPlane.APIServer.StopTimeout = te.ControlPlaneStopTimeout
log.V(1).Info("starting control plane", "api server flags", te.ControlPlane.APIServer.Args)
if err := te.startControlPlane(); err != nil {
return nil, err
}
// Create the *rest.Config for creating new clients
te.Config = &rest.Config{
Host: te.ControlPlane.APIURL().Host,
}
}
log.V(1).Info("installing CRDs")
_, err := InstallCRDs(te.Config, CRDInstallOptions{
Paths: te.CRDDirectoryPaths,
CRDs: te.CRDs,
})
return te.Config, err
}
func (te *Environment) startControlPlane() error {
numTries, maxRetries := 0, 5
var err error
for ; numTries < maxRetries; numTries++ {
// Start the control plane - retry if it fails
err = te.ControlPlane.Start()
if err == nil {
break
}
log.Error(err, "unable to start the controlplane", "tries", numTries)
}
if numTries == maxRetries {
return fmt.Errorf("failed to start the controlplane. retried %d times: %v", numTries, err)
}
return nil
}
func (te *Environment) defaultTimeouts() error {
var err error
if te.ControlPlaneStartTimeout == 0 {
if envVal := os.Getenv(envStartTimeout); envVal != "" {
te.ControlPlaneStartTimeout, err = time.ParseDuration(envVal)
if err != nil {
return err
}
} else {
te.ControlPlaneStartTimeout = defaultKubebuilderControlPlaneStartTimeout
}
}
if te.ControlPlaneStopTimeout == 0 {
if envVal := os.Getenv(envStopTimeout); envVal != "" {
te.ControlPlaneStopTimeout, err = time.ParseDuration(envVal)
if err != nil {
return err
}
} else {
te.ControlPlaneStopTimeout = defaultKubebuilderControlPlaneStopTimeout
}
}
return nil
}

View file

@ -0,0 +1,126 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package log
import (
"github.com/go-logr/logr"
)
// loggerPromise knows how to populate a concrete logr.Logger
// with options, given an actual base logger later on down the line.
type loggerPromise struct {
logger *DelegatingLogger
childPromises []*loggerPromise
name *string
tags []interface{}
}
// WithName provides a new Logger with the name appended
func (p *loggerPromise) WithName(l *DelegatingLogger, name string) *loggerPromise {
res := &loggerPromise{
logger: l,
name: &name,
}
p.childPromises = append(p.childPromises, res)
return res
}
// WithValues provides a new Logger with the tags appended
func (p *loggerPromise) WithValues(l *DelegatingLogger, tags ...interface{}) *loggerPromise {
res := &loggerPromise{
logger: l,
tags: tags,
}
p.childPromises = append(p.childPromises, res)
return res
}
// Fulfill instantiates the Logger with the provided logger
func (p *loggerPromise) Fulfill(parentLogger logr.Logger) {
var logger = parentLogger
if p.name != nil {
logger = logger.WithName(*p.name)
}
if p.tags != nil {
logger = logger.WithValues(p.tags...)
}
p.logger.Logger = logger
p.logger.promise = nil
for _, childPromise := range p.childPromises {
childPromise.Fulfill(logger)
}
}
// DelegatingLogger is a logr.Logger that delegates to another logr.Logger.
// If the underlying promise is not nil, it registers calls to sub-loggers with
// the logging factory to be populated later, and returns a new delegating
// logger. It expects to have *some* logr.Logger set at all times (generally
// a no-op logger before the promises are fulfilled).
type DelegatingLogger struct {
logr.Logger
promise *loggerPromise
}
// WithName provides a new Logger with the name appended
func (l *DelegatingLogger) WithName(name string) logr.Logger {
if l.promise == nil {
return l.Logger.WithName(name)
}
res := &DelegatingLogger{Logger: l.Logger}
promise := l.promise.WithName(res, name)
res.promise = promise
return res
}
// WithValues provides a new Logger with the tags appended
func (l *DelegatingLogger) WithValues(tags ...interface{}) logr.Logger {
if l.promise == nil {
return l.Logger.WithValues(tags...)
}
res := &DelegatingLogger{Logger: l.Logger}
promise := l.promise.WithValues(res, tags...)
res.promise = promise
return res
}
// Fulfill switches the logger over to use the actual logger
// provided, instead of the temporary initial one, if this method
// has not been previously called.
func (l *DelegatingLogger) Fulfill(actual logr.Logger) {
if l.promise != nil {
l.promise.Fulfill(actual)
}
}
// NewDelegatingLogger constructs a new DelegatingLogger which uses
// the given logger before it's promise is fulfilled.
func NewDelegatingLogger(initial logr.Logger) *DelegatingLogger {
l := &DelegatingLogger{
Logger: initial,
promise: &loggerPromise{},
}
l.promise.logger = l
return l
}

View file

@ -0,0 +1,129 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package log contains utilities for fetching a new logger
// when one is not already available.
package log
import (
"fmt"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
)
// KubeAwareEncoder is a Kubernetes-aware Zap Encoder.
// Instead of trying to force Kubernetes objects to implement
// ObjectMarshaller, we just implement a wrapper around a normal
// ObjectMarshaller that checks for Kubernetes objects.
type KubeAwareEncoder struct {
// Encoder is the zapcore.Encoder that this encoder delegates to
zapcore.Encoder
// Verbose controls whether or not the full object is printed.
// If false, only name, namespace, api version, and kind are printed.
// Otherwise, the full object is logged.
Verbose bool
}
// namespacedNameWrapper is a zapcore.ObjectMarshaler for Kubernetes NamespacedName
type namespacedNameWrapper struct {
types.NamespacedName
}
func (w namespacedNameWrapper) MarshalLogObject(enc zapcore.ObjectEncoder) error {
if w.Namespace != "" {
enc.AddString("namespace", w.Namespace)
}
enc.AddString("name", w.Name)
return nil
}
// kubeObjectWrapper is a zapcore.ObjectMarshaler for Kubernetes objects.
type kubeObjectWrapper struct {
obj runtime.Object
}
// MarshalLogObject implements zapcore.ObjectMarshaler
func (w kubeObjectWrapper) MarshalLogObject(enc zapcore.ObjectEncoder) error {
// TODO(directxman12): log kind and apiversion if not set explicitly (common case)
// -- needs an a scheme to convert to the GVK.
gvk := w.obj.GetObjectKind().GroupVersionKind()
if gvk.Version != "" {
enc.AddString("apiVersion", gvk.GroupVersion().String())
enc.AddString("kind", gvk.Kind)
}
objMeta, err := meta.Accessor(w.obj)
if err != nil {
return fmt.Errorf("got runtime.Object without object metadata: %v", w.obj)
}
ns := objMeta.GetNamespace()
if ns != "" {
enc.AddString("namespace", ns)
}
enc.AddString("name", objMeta.GetName())
return nil
}
// NB(directxman12): can't just override AddReflected, since the encoder calls AddReflected on itself directly
// Clone implements zapcore.Encoder
func (k *KubeAwareEncoder) Clone() zapcore.Encoder {
return &KubeAwareEncoder{
Encoder: k.Encoder.Clone(),
}
}
// EncodeEntry implements zapcore.Encoder
func (k *KubeAwareEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
if k.Verbose {
// Kubernetes objects implement fmt.Stringer, so if we
// want verbose output, just delegate to that.
return k.Encoder.EncodeEntry(entry, fields)
}
for i, field := range fields {
// intercept stringer fields that happen to be Kubernetes runtime.Object or
// types.NamespacedName values (Kubernetes runtime.Objects commonly
// implement String, apparently).
if field.Type == zapcore.StringerType {
switch val := field.Interface.(type) {
case runtime.Object:
fields[i] = zapcore.Field{
Type: zapcore.ObjectMarshalerType,
Key: field.Key,
Interface: kubeObjectWrapper{obj: val},
}
case types.NamespacedName:
fields[i] = zapcore.Field{
Type: zapcore.ObjectMarshalerType,
Key: field.Key,
Interface: namespacedNameWrapper{NamespacedName: val},
}
}
}
}
return k.Encoder.EncodeEntry(entry, fields)
}

View file

@ -0,0 +1,85 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package log contains utilities for fetching a new logger
// when one is not already available.
package log
import (
"io"
"os"
"time"
"github.com/go-logr/logr"
"github.com/go-logr/zapr"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// ZapLogger is a Logger implementation.
// If development is true, a Zap development config will be used
// (stacktraces on warnings, no sampling), otherwise a Zap production
// config will be used (stacktraces on errors, sampling).
func ZapLogger(development bool) logr.Logger {
return ZapLoggerTo(os.Stderr, development)
}
// ZapLoggerTo returns a new Logger implementation using Zap which logs
// to the given destination, instead of stderr. It otherise behaves like
// ZapLogger.
func ZapLoggerTo(destWriter io.Writer, development bool) logr.Logger {
// this basically mimics New<type>Config, but with a custom sink
sink := zapcore.AddSync(destWriter)
var enc zapcore.Encoder
var lvl zap.AtomicLevel
var opts []zap.Option
if development {
encCfg := zap.NewDevelopmentEncoderConfig()
enc = zapcore.NewConsoleEncoder(encCfg)
lvl = zap.NewAtomicLevelAt(zap.DebugLevel)
opts = append(opts, zap.Development(), zap.AddStacktrace(zap.ErrorLevel))
} else {
encCfg := zap.NewProductionEncoderConfig()
enc = zapcore.NewJSONEncoder(encCfg)
lvl = zap.NewAtomicLevelAt(zap.InfoLevel)
opts = append(opts, zap.AddStacktrace(zap.WarnLevel),
zap.WrapCore(func(core zapcore.Core) zapcore.Core {
return zapcore.NewSampler(core, time.Second, 100, 100)
}))
}
opts = append(opts, zap.AddCallerSkip(1), zap.ErrorOutput(sink))
log := zap.New(zapcore.NewCore(&KubeAwareEncoder{Encoder: enc, Verbose: development}, sink, lvl))
log = log.WithOptions(opts...)
return zapr.NewLogger(log)
}
// SetLogger sets a concrete logging implementation for all deferred Loggers.
func SetLogger(l logr.Logger) {
Log.Fulfill(l)
}
// Log is the base logger used by kubebuilder. It delegates
// to another logr.Logger. You *must* call SetLogger to
// get any actual logging.
var Log = NewDelegatingLogger(NullLogger{})
// KBLog is a base parent logger.
var KBLog logr.Logger
func init() {
KBLog = Log.WithName("kubebuilder")
}

View file

@ -0,0 +1,60 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package log
import (
"github.com/go-logr/logr"
)
// NB: this is the same as the null logger logr/testing,
// but avoids accidentally adding the testing flags to
// all binaries.
// NullLogger is a logr.Logger that does nothing.
type NullLogger struct{}
var _ logr.Logger = NullLogger{}
// Info implements logr.InfoLogger
func (NullLogger) Info(_ string, _ ...interface{}) {
// Do nothing.
}
// Enabled implements logr.InfoLogger
func (NullLogger) Enabled() bool {
return false
}
// Error implements logr.Logger
func (NullLogger) Error(_ error, _ string, _ ...interface{}) {
// Do nothing.
}
// V implements logr.Logger
func (log NullLogger) V(_ int) logr.InfoLogger {
return log
}
// WithName implements logr.Logger
func (log NullLogger) WithName(_ string) logr.Logger {
return log
}
// WithValues implements logr.Logger
func (log NullLogger) WithValues(_ ...interface{}) logr.Logger {
return log
}