Merge pull request #45 from digitalocean/varsha/context-timeout

Allow users to configure timeout while building kubernetes client
image-warning-sha256 v0.1.0
Varsha Varadarajan 2019-07-22 17:44:05 -04:00 committed by GitHub
commit 80b6b4a8b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 118 additions and 40 deletions

View File

@ -17,8 +17,6 @@ limitations under the License.
package basic
import (
"fmt"
"github.com/digitalocean/clusterlint/checks"
"github.com/digitalocean/clusterlint/kube"
)
@ -50,7 +48,6 @@ func (b *barePodCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
var diagnostics []checks.Diagnostic
for _, pod := range objects.Pods.Items {
pod := pod
fmt.Println(pod.ObjectMeta.OwnerReferences)
if len(pod.ObjectMeta.OwnerReferences) == 0 {
d := checks.Diagnostic{
Check: b.Name(),

View File

@ -17,6 +17,7 @@ limitations under the License.
package checks
import (
"context"
"errors"
"sync"
@ -25,8 +26,8 @@ import (
)
// Run applies the filters and runs the resultant check list in parallel
func Run(client *kube.Client, checkFilter CheckFilter, diagnosticFilter DiagnosticFilter) ([]Diagnostic, error) {
objects, err := client.FetchObjects()
func Run(ctx context.Context, client *kube.Client, checkFilter CheckFilter, diagnosticFilter DiagnosticFilter) ([]Diagnostic, error) {
objects, err := client.FetchObjects(ctx)
if err != nil {
return nil, err
}

View File

@ -17,10 +17,12 @@ limitations under the License.
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
"github.com/digitalocean/clusterlint/checks"
"github.com/digitalocean/clusterlint/kube"
@ -34,7 +36,7 @@ import (
func main() {
app := cli.NewApp()
app.Name = "clusterlint"
app.Usage = "Linter for k8sobjects from a live cluster"
app.Usage = "Linter for k8s objects from a live cluster"
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "kubeconfig",
@ -45,6 +47,11 @@ func main() {
Name: "context",
Usage: "context for the kubernetes client. default: current context",
},
cli.DurationFlag{
Name: "timeout",
Usage: "configure timeout for the kubernetes client. default: 30s",
Value: time.Second * 30,
},
}
app.Commands = []cli.Command{
{
@ -126,7 +133,7 @@ func listChecks(c *cli.Context) error {
// runChecks runs all the checks based on the flags passed.
func runChecks(c *cli.Context) error {
client, err := kube.NewClient(c.GlobalString("kubeconfig"), c.GlobalString("context"))
client, err := kube.NewClient(kube.WithConfigFile(c.GlobalString("kubeconfig")), kube.WithKubeContext(c.GlobalString("context")), kube.WithTimeout(c.GlobalDuration("timeout")))
if err != nil {
return err
}
@ -138,7 +145,7 @@ func runChecks(c *cli.Context) error {
diagnosticFilter := checks.DiagnosticFilter{Severity: checks.Severity(c.String("level"))}
diagnostics, err := checks.Run(client, filter, diagnosticFilter)
diagnostics, err := checks.Run(context.Background(), client, filter, diagnosticFilter)
write(diagnostics, c)

View File

@ -17,6 +17,9 @@ limitations under the License.
package kube
import (
"context"
"errors"
"golang.org/x/sync/errgroup"
ar "k8s.io/api/admissionregistration/v1beta1"
corev1 "k8s.io/api/core/v1"
@ -57,7 +60,8 @@ type Client struct {
}
// FetchObjects returns the objects from a Kubernetes cluster.
func (c *Client) FetchObjects() (*Objects, error) {
// ctx is currently unused during API calls. More info: https://github.com/kubernetes/community/pull/1166
func (c *Client) FetchObjects(ctx context.Context) (*Objects, error) {
client := c.KubeClient.CoreV1()
admissionControllerClient := c.KubeClient.AdmissionregistrationV1beta1()
opts := metav1.ListOptions{}
@ -135,46 +139,43 @@ func (c *Client) FetchObjects() (*Objects, error) {
}
// NewClient builds a kubernetes client to interact with the live cluster.
// The kube config file path and the context must be specified for the client
// The kube config file path or the kubeconfig yaml must be specified
// If not specified, defaults are assumed - configPath: ~/.kube/config, configContext: current context
func NewClient(configPath, configContext string) (*Client, error) {
func NewClient(opts ...Option) (*Client, error) {
defOpts := &options{}
for _, opt := range opts {
if err := opt(defOpts); err != nil {
return nil, err
}
}
var config *rest.Config
var err error
if defOpts.yaml != nil && defOpts.path != "" {
return nil, errors.New("cannot specify both yaml and kubeconfg file path")
}
if configContext != "" {
config, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: configPath},
&clientcmd.ConfigOverrides{
CurrentContext: configContext,
}).ClientConfig()
if defOpts.yaml != nil {
config, err = clientcmd.RESTConfigFromKubeConfig(defOpts.yaml)
} else if defOpts.path != "" {
if defOpts.kubeContext != "" {
config, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: defOpts.path},
&clientcmd.ConfigOverrides{
CurrentContext: defOpts.kubeContext,
}).ClientConfig()
} else {
config, err = clientcmd.BuildConfigFromFlags("", defOpts.path)
}
} else {
config, err = clientcmd.BuildConfigFromFlags("", configPath)
err = errors.New("cannot authenticate Kubernetes API requests")
}
if err != nil {
return nil, err
}
client, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
return &Client{
KubeClient: client,
}, nil
}
// BuildClient builds a kubernetes client from the yaml to interact with the live cluster.
func BuildClient(yaml []byte) (*Client, error) {
var config *rest.Config
var err error
config, err = clientcmd.RESTConfigFromKubeConfig(yaml)
if err != nil {
return nil, err
}
config.Timeout = defOpts.timeout
client, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err

View File

@ -17,6 +17,8 @@ limitations under the License.
package kube
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
@ -37,7 +39,7 @@ func TestFetchObjects(t *testing.T) {
Labels: map[string]string{"doks_key": "bar"}},
})
actual, err := api.FetchObjects()
actual, err := api.FetchObjects(context.Background())
assert.NoError(t, err)
assert.NotNil(t, actual.Nodes)
@ -56,3 +58,12 @@ func TestFetchObjects(t *testing.T) {
assert.NotNil(t, actual.MutatingWebhookConfigurations)
assert.NotNil(t, actual.SystemNamespace)
}
func TestNewClientErrors(t *testing.T) {
// test both yaml and filepath specified
_, err := NewClient(WithConfigFile("some-path"), WithYaml([]byte("yaml")))
assert.Equal(t, errors.New("cannot specify both yaml and kubeconfg file path"), err)
// test no authentication mechanism
_, err = NewClient()
assert.Equal(t, errors.New("cannot authenticate Kubernetes API requests"), err)
}

61
kube/options.go Normal file
View File

@ -0,0 +1,61 @@
/*
Copyright 2019 DigitalOcean
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 kube
import "time"
type options struct {
path string
kubeContext string
yaml []byte
timeout time.Duration
}
// Option function that allows injecting options while building kube.Client.
type Option func(*options) error
// WithConfigFile returns an Option injected with a config file path.
func WithConfigFile(path string) Option {
return func(o *options) error {
o.path = path
return nil
}
}
// WithKubeContext returns an Option injected with a kubernetes context.
func WithKubeContext(kubeContext string) Option {
return func(o *options) error {
o.kubeContext = kubeContext
return nil
}
}
// WithYaml returns an Option injected with a kubeconfig yaml.
func WithYaml(yaml []byte) Option {
return func(o *options) error {
o.yaml = yaml
return nil
}
}
// WithTimeout returns an Option injected with a timeout option while building client.
func WithTimeout(t time.Duration) Option {
return func(o *options) error {
o.timeout = t
return nil
}
}