Adding dynamic-client interface

This commit is contained in:
Barni S 2019-04-30 14:00:01 -04:00 committed by jingfangliu
parent 2aa93f9c3c
commit dd28dd9c37
12 changed files with 338 additions and 15 deletions

11
Makefile Normal file
View File

@ -0,0 +1,11 @@
generate:
GO111MODULE=on go generate
run:
GO111MODULE=on go run .
build:
GO111MODULE=on go build .
check:
GO111MODULE=on ./scripts/check-everything.sh

3
go.mod
View File

@ -17,7 +17,7 @@ require (
github.com/google/go-github v17.0.0+incompatible // indirect
github.com/google/go-querystring v1.0.0 // indirect
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
github.com/google/wire v0.2.1
github.com/google/wire v0.2.2-0.20190423202733-d079521b6f51
github.com/googleapis/gnostic v0.2.0 // indirect
github.com/gophercloud/gophercloud v0.0.0-20190328150603-33e54f40ffcf // indirect
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
@ -37,7 +37,6 @@ require (
go.uber.org/atomic v1.3.2 // indirect
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.9.1 // indirect
golang.org/x/net v0.0.0-20190328230028-74de082e2cca // indirect
golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914 // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect

11
go.sum
View File

@ -83,6 +83,8 @@ github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeq
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/wire v0.2.1 h1:TYj4Z2qjqxa2ufb34UJqVeO9aznL+i0fLO6TqThKZ7Y=
github.com/google/wire v0.2.1/go.mod h1:ptBl5bWD3nzmJHVNwYHV3v4wdtKzBMlU2YbtKQCG9GI=
github.com/google/wire v0.2.2-0.20190423202733-d079521b6f51 h1:oah2osnQk2JwZUU9BasNK201Ea2oNDY/3is4GYtnR7k=
github.com/google/wire v0.2.2-0.20190423202733-d079521b6f51/go.mod h1:7FHVg6mFpFQrjeUZrm+BaD50N5jnDKm50uVPTpyYOmU=
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.0.0-20190328150603-33e54f40ffcf h1:Cadz8DdkvBq1XLIk6aDDLsYHfXFr9sjlHE0RmaZpY9Q=
@ -201,6 +203,8 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd h1:sMHc2rZHuzQmrbVoSpt9HgerkXPyIeCSO6k0zUMGfFk=
golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -220,6 +224,9 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190328230028-74de082e2cca h1:hyA6yiAgbUwuWqtscNvWAI7U1CtlaD1KilQ6iudt1aI=
golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6 h1:HdqqaWmYAUI7/dmByKKEw+yxDksGSo+9GjkUc9Zp34E=
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -243,6 +250,9 @@ golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
@ -254,6 +264,7 @@ golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.2.0 h1:B5VXkdjt7K2Gm6fGBC9C9a1OAKJDT95cTqwet+2zib0=
google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU=

View File

@ -0,0 +1,200 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package client
import (
"context"
"fmt"
"strings"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
)
// NewForConfig returns a new Client using the provided config and Options.
// The returned client reads *and* writes directly from the server
// (it doesn't use object caches). It understands how to work with
// normal types (both custom resources and aggregated/built-in resources),
// as well as unstructured types.
//
// In the case of normal types, the scheme will be used to look up the
// corresponding group, version, and kind for the given type. In the
// case of unstrctured types, the group, version, and kind will be extracted
// from the corresponding fields on the object.
func NewForConfig(dynamicClient dynamic.Interface, mapper meta.RESTMapper) (Client, error) {
c := &client{
client: dynamicClient,
restMapper: mapper,
}
return c, nil
}
var _ Client = &client{}
// client is a client.Client that reads and writes directly from/to an API server. It lazily initializes
// new clients at the time they are used, and caches the client.
type client struct {
client dynamic.Interface
restMapper meta.RESTMapper
}
// Create implements client.Client
func (uc *client) Create(_ context.Context, obj runtime.Object, options metav1.CreateOptions) error {
u, ok := obj.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("unstructured client did not understand object: %T", obj)
}
r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace())
if err != nil {
return err
}
i, err := r.Create(u, options)
if err != nil {
return err
}
u.Object = i.Object
return nil
}
// Update implements client.Client
func (uc *client) Update(_ context.Context, obj runtime.Object, options metav1.UpdateOptions) error {
u, ok := obj.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("unstructured client did not understand object: %T", obj)
}
r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace())
if err != nil {
return err
}
i, err := r.Update(u, options)
if err != nil {
return err
}
u.Object = i.Object
return nil
}
// Delete implements client.Client
func (uc *client) Delete(_ context.Context, obj runtime.Object, options *metav1.DeleteOptions) error {
u, ok := obj.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("unstructured client did not understand object: %T", obj)
}
r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace())
if err != nil {
return err
}
err = r.Delete(u.GetName(), options)
return err
}
// Patch implements client.Client
func (uc *client) Patch(_ context.Context, obj runtime.Object, patch Patch, options metav1.PatchOptions) error {
u, ok := obj.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("unstructured client did not understand object: %T", obj)
}
r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace())
if err != nil {
return err
}
data, err := patch.Data(obj)
if err != nil {
return err
}
i, err := r.Patch(u.GetName(), patch.Type(), data, options)
if err != nil {
return err
}
u.Object = i.Object
return nil
}
// Get implements client.Client
func (uc *client) Get(_ context.Context, key types.NamespacedName, obj runtime.Object) error {
u, ok := obj.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("unstructured client did not understand object: %T", obj)
}
r, err := uc.getResourceInterface(u.GroupVersionKind(), key.Namespace)
if err != nil {
return err
}
i, err := r.Get(key.Name, metav1.GetOptions{})
if err != nil {
return err
}
u.Object = i.Object
return nil
}
// List implements client.Client
func (uc *client) List(_ context.Context, obj runtime.Object, namespace string, options metav1.ListOptions) error {
u, ok := obj.(*unstructured.UnstructuredList)
if !ok {
return fmt.Errorf("unstructured client did not understand object: %T", obj)
}
gvk := u.GroupVersionKind()
if strings.HasSuffix(gvk.Kind, "List") {
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
}
r, err := uc.getResourceInterface(gvk, namespace)
if err != nil {
return err
}
i, err := r.List(options)
if err != nil {
return err
}
u.Items = i.Items
u.Object = i.Object
return nil
}
func (uc *client) UpdateStatus(_ context.Context, obj runtime.Object) error {
u, ok := obj.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("unstructured client did not understand object: %T", obj)
}
r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace())
if err != nil {
return err
}
i, err := r.UpdateStatus(u, metav1.UpdateOptions{})
if err != nil {
return err
}
u.Object = i.Object
return nil
}
func (uc *client) getResourceInterface(gvk schema.GroupVersionKind, ns string) (dynamic.ResourceInterface, error) {
mapping, err := uc.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
return nil, err
}
if mapping.Scope.Name() == meta.RESTScopeNameRoot {
return uc.client.Resource(mapping.Resource), nil
}
return uc.client.Resource(mapping.Resource).Namespace(ns), nil
}

View File

@ -0,0 +1,75 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package client
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
)
// Patch is a patch that can be applied to a Kubernetes object.
type Patch interface {
// Type is the PatchType of the patch.
Type() types.PatchType
// Data is the raw data representing the patch.
Data(obj runtime.Object) ([]byte, error)
}
// Reader knows how to read and list Kubernetes objects.
type Reader interface {
// Get retrieves an obj for the given object key from the Kubernetes Cluster.
// obj must be a struct pointer so that obj can be updated with the response
// returned by the Server.
Get(ctx context.Context, key types.NamespacedName, obj runtime.Object) error
// List retrieves list of objects for a given namespace and list options. On a
// successful call, Items field in the list will be populated with the
// result returned from the server.
List(ctx context.Context, list runtime.Object, namespace string, options metav1.ListOptions) error
}
// Writer knows how to create, delete, and update Kubernetes objects.
type Writer interface {
// Create saves the object obj in the Kubernetes cluster.
Create(ctx context.Context, obj runtime.Object, options metav1.CreateOptions) error
// Delete deletes the given obj from Kubernetes cluster.
Delete(ctx context.Context, obj runtime.Object, options *metav1.DeleteOptions) error
// Update updates the given obj in the Kubernetes cluster. obj must be a
// struct pointer so that obj can be updated with the content returned by the Server.
Update(ctx context.Context, obj runtime.Object, options metav1.UpdateOptions) error
// Patch patches the given obj in the Kubernetes cluster. obj must be a
// struct pointer so that obj can be updated with the content returned by the Server.
Patch(ctx context.Context, obj runtime.Object, patch Patch, options metav1.PatchOptions) error
}
// StatusWriter knows how to update status subresource of a Kubernetes object.
type StatusWriter interface {
// UpdateStatus updates the fields corresponding to the status subresource for the
// given obj. obj must be a struct pointer so that obj can be updated
// with the content returned by the Server.
UpdateStatus(ctx context.Context, obj runtime.Object) error
}
// Client knows how to perform CRUD operations on Kubernetes objects.
type Client interface {
Reader
Writer
StatusWriter
}

View File

@ -29,9 +29,11 @@ import (
)
// ProviderSet provides wiring for initializing types.
var ProviderSet = wire.NewSet(
output.CommandOutputWriter{}, list.CommandLister{}, parse.CommandParser{}, dispatch.Dispatcher{},
CommandBuilder{})
var ProviderSet = wire.NewSet(wire.Struct(new(output.CommandOutputWriter), "*"),
wire.Struct(new(list.CommandLister), "*"),
wire.Struct(new(parse.CommandParser), "*"),
wire.Struct(new(dispatch.Dispatcher), "*"),
wire.Struct(new(CommandBuilder), "*"))
// CommandBuilder creates dynamically generated commands from annotations on CRDs.
type CommandBuilder struct {

View File

@ -26,7 +26,7 @@ import (
"sigs.k8s.io/cli-experimental/internal/pkg/status"
)
// InitializeApplyStatus creates a new *status.Status object
// InitializeStatus creates a new *status.Status object
func InitializeStatus(clik8s.ResourceConfigPath, io.Writer, util.Args) (*status.Status, error) {
panic(wire.Build(ProviderSet))
}

View File

@ -25,7 +25,7 @@ import (
// ProviderSet defines dependencies for initializing objects
var ProviderSet = wire.NewSet(wirek8s.ProviderSet, wiregit.OptionalProviderSet,
status.Status{}, apply.Apply{}, NewStatusCommandResult, NewApplyCommandResult)
wire.Struct(new(status.Status), "*"), wire.Struct(new(apply.Apply), "*"), NewStatusCommandResult, NewApplyCommandResult)
// NewStatusCommandResult returns a new status.Result
func NewStatusCommandResult(s *status.Status, out io.Writer) (status.Result, error) {

View File

@ -24,14 +24,17 @@ import (
"github.com/google/wire"
"github.com/spf13/pflag"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/kustomize"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"sigs.k8s.io/cli-experimental/internal/pkg/client"
"sigs.k8s.io/cli-experimental/internal/pkg/clik8s"
"sigs.k8s.io/cli-experimental/internal/pkg/util"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/yaml"
@ -40,8 +43,8 @@ import (
)
// ProviderSet defines dependencies for initializing Kubernetes objects
var ProviderSet = wire.NewSet(NewKubernetesClientSet, NewExtensionsClientSet, NewConfigFlags, NewRestConfig,
NewResourceConfig, NewFileSystem, NewDynamicClient)
var ProviderSet = wire.NewSet(NewKubernetesClientSet, NewExtensionsClientSet, NewConfigFlags, NewRestConfig, NewDynamicClient, NewRestMapper,
NewResourceConfig, NewFileSystem, NewClient)
// Flags registers flags for talkig to a Kubernetes cluster
func Flags(fs *pflag.FlagSet) {
@ -130,6 +133,16 @@ func NewDynamicClient(c *rest.Config) (dynamic.Interface, error) {
return dynamic.NewForConfig(c)
}
// NewRestMapper provides a dynamic.Interface
func NewRestMapper(c *rest.Config) (meta.RESTMapper, error) {
return apiutil.NewDiscoveryRESTMapper(c)
}
// NewClient provides a dynamic.Interface
func NewClient(d dynamic.Interface, m meta.RESTMapper) (client.Client, error) {
return client.NewForConfig(d, m)
}
// NewFileSystem provides a real FileSystem
func NewFileSystem() fs.FileSystem {
return fs.MakeRealFS()

View File

@ -26,7 +26,7 @@ import (
// ProviderSet defines dependencies for initializing objects
var ProviderSet = wire.NewSet(
dy.ProviderSet, wirek8s.NewKubernetesClientSet, wirek8s.NewExtensionsClientSet, wirek8s.NewDynamicClient,
NewRestConfig, status.Status{}, apply.Apply{})
NewRestConfig, wire.Struct(new(status.Status), "*"), wire.Struct(new(apply.Apply), "*"))
// NewRestConfig provides a rest.Config for a testing environment
func NewRestConfig() (*rest.Config, func(), error) {

View File

@ -63,9 +63,22 @@ function fetch_kb_tools {
tar -zvxf "$kb_tools_archive_path" -C "$tmp_root/"
}
# Install metalinter
function install_metalinter {
which gometalinter.v2 || go get -v -u gopkg.in/alecthomas/gometalinter.v2
}
# Install wire
function install_wire {
which wire|| go get -v -u github.com/google/wire/cmd/wire
}
header_text "using tools"
which gometalinter.v2
install_wire
install_metalinter
fetch_kb_tools
setup_envs

View File

@ -18,14 +18,13 @@ set -e
source $(dirname ${BASH_SOURCE})/common.sh
header_text "populating vendor for gometalinter.v2"
go mod vendor
header_text "running go vet"
go vet ./internal/... ./pkg/... ./cmd/... ./util/...
header_text "populating vendor for gometalinter.v2"
go mod vendor
header_text "running gometalinter.v2"
gometalinter.v2 -e $(go env GOROOT) -e vendor/ -e _gen.go --disable-all \