From 4e70d5e2f6c5dbd242b65bd8b1e3ae2c226910a7 Mon Sep 17 00:00:00 2001 From: Daishan Peng Date: Wed, 8 Nov 2017 11:06:55 -0700 Subject: [PATCH 1/2] update vendor(incluing hack) --- vendor.conf | 5 +- .../cluster-controller/.dockerignore | 4 + .../alena1108/cluster-controller/.drone.yml | 9 + .../alena1108/cluster-controller/.gitignore | 6 + .../cluster-controller/Dockerfile.dapper | 31 + .../alena1108/cluster-controller/LICENSE | 177 ++++ .../alena1108/cluster-controller/Makefile | 23 + .../alena1108/cluster-controller/README.md | 28 + .../cluster-controller/client/v1/client.go | 63 ++ .../cluster-controller/client/v1/cluster.go | 184 ++++ .../client/v1/clusternode.go | 184 ++++ .../cluster-controller/client/v1/types.go | 210 +++++ .../alena1108/cluster-controller/vendor.conf | 10 + vendor/github.com/pkg/errors/.gitignore | 24 + vendor/github.com/pkg/errors/.travis.yml | 13 + vendor/github.com/pkg/errors/LICENSE | 23 + vendor/github.com/pkg/errors/README.md | 52 ++ vendor/github.com/pkg/errors/appveyor.yml | 32 + vendor/github.com/pkg/errors/errors.go | 269 ++++++ vendor/github.com/pkg/errors/stack.go | 186 ++++ vendor/gopkg.in/check.v1/.gitignore | 4 + vendor/gopkg.in/check.v1/.travis.yml | 3 + vendor/gopkg.in/check.v1/LICENSE | 25 + vendor/gopkg.in/check.v1/README.md | 20 + vendor/gopkg.in/check.v1/TODO | 2 + vendor/gopkg.in/check.v1/benchmark.go | 187 ++++ vendor/gopkg.in/check.v1/check.go | 873 ++++++++++++++++++ vendor/gopkg.in/check.v1/checkers.go | 458 +++++++++ vendor/gopkg.in/check.v1/helpers.go | 231 +++++ vendor/gopkg.in/check.v1/printer.go | 168 ++++ vendor/gopkg.in/check.v1/reporter.go | 88 ++ vendor/gopkg.in/check.v1/run.go | 175 ++++ vendor/k8s.io/client-go/dynamic/BUILD | 55 ++ vendor/k8s.io/client-go/dynamic/client.go | 306 ++++++ .../k8s.io/client-go/dynamic/client_pool.go | 122 +++ .../k8s.io/client-go/dynamic/dynamic_util.go | 96 ++ 36 files changed, 4345 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/alena1108/cluster-controller/.dockerignore create mode 100644 vendor/github.com/alena1108/cluster-controller/.drone.yml create mode 100644 vendor/github.com/alena1108/cluster-controller/.gitignore create mode 100644 vendor/github.com/alena1108/cluster-controller/Dockerfile.dapper create mode 100644 vendor/github.com/alena1108/cluster-controller/LICENSE create mode 100644 vendor/github.com/alena1108/cluster-controller/Makefile create mode 100644 vendor/github.com/alena1108/cluster-controller/README.md create mode 100644 vendor/github.com/alena1108/cluster-controller/client/v1/client.go create mode 100644 vendor/github.com/alena1108/cluster-controller/client/v1/cluster.go create mode 100644 vendor/github.com/alena1108/cluster-controller/client/v1/clusternode.go create mode 100644 vendor/github.com/alena1108/cluster-controller/client/v1/types.go create mode 100644 vendor/github.com/alena1108/cluster-controller/vendor.conf create mode 100644 vendor/github.com/pkg/errors/.gitignore create mode 100644 vendor/github.com/pkg/errors/.travis.yml create mode 100644 vendor/github.com/pkg/errors/LICENSE create mode 100644 vendor/github.com/pkg/errors/README.md create mode 100644 vendor/github.com/pkg/errors/appveyor.yml create mode 100644 vendor/github.com/pkg/errors/errors.go create mode 100644 vendor/github.com/pkg/errors/stack.go create mode 100644 vendor/gopkg.in/check.v1/.gitignore create mode 100644 vendor/gopkg.in/check.v1/.travis.yml create mode 100644 vendor/gopkg.in/check.v1/LICENSE create mode 100644 vendor/gopkg.in/check.v1/README.md create mode 100644 vendor/gopkg.in/check.v1/TODO create mode 100644 vendor/gopkg.in/check.v1/benchmark.go create mode 100644 vendor/gopkg.in/check.v1/check.go create mode 100644 vendor/gopkg.in/check.v1/checkers.go create mode 100644 vendor/gopkg.in/check.v1/helpers.go create mode 100644 vendor/gopkg.in/check.v1/printer.go create mode 100644 vendor/gopkg.in/check.v1/reporter.go create mode 100644 vendor/gopkg.in/check.v1/run.go create mode 100644 vendor/k8s.io/client-go/dynamic/BUILD create mode 100644 vendor/k8s.io/client-go/dynamic/client.go create mode 100644 vendor/k8s.io/client-go/dynamic/client_pool.go create mode 100644 vendor/k8s.io/client-go/dynamic/dynamic_util.go diff --git a/vendor.conf b/vendor.conf index 325ee9fee..ef73d58fc 100644 --- a/vendor.conf +++ b/vendor.conf @@ -7,4 +7,7 @@ golang.org/x/oauth2 bb50c06baba3d0c76f9d125c0719093e315b5b44 google.golang.org/api c5d94a018003dc535877ebf36b0ed720c690202a google.golang.org/grpc v1.7.1 golang.org/x/text 6eab0e8f74e86c598ec3b6fad4888e0c11482d48 -google.golang.org/genproto f676e0f3ac6395ff1a529ae59a6670878a8371a6 \ No newline at end of file +google.golang.org/genproto f676e0f3ac6395ff1a529ae59a6670878a8371a6 +github.com/alena1108/cluster-controller 500dab172104a40451ddd94541efcf5f5cd6c86b https://github.com/StrongMonkey/cluster-controller.git +github.com/pkg/errors f15c970de5b76fac0b59abb32d62c17cc7bed265 +gopkg.in/check.v1 20d25e2804050c1cd24a7eea1e7a6447dd0e74ec \ No newline at end of file diff --git a/vendor/github.com/alena1108/cluster-controller/.dockerignore b/vendor/github.com/alena1108/cluster-controller/.dockerignore new file mode 100644 index 000000000..6e43c2a99 --- /dev/null +++ b/vendor/github.com/alena1108/cluster-controller/.dockerignore @@ -0,0 +1,4 @@ +./bin +./.dapper +./dist +./.trash-cache diff --git a/vendor/github.com/alena1108/cluster-controller/.drone.yml b/vendor/github.com/alena1108/cluster-controller/.drone.yml new file mode 100644 index 000000000..8f5c37cda --- /dev/null +++ b/vendor/github.com/alena1108/cluster-controller/.drone.yml @@ -0,0 +1,9 @@ +--- +pipeline: + build: + privileged: true + image: rancher/dapper:1.11.2 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - dapper ci diff --git a/vendor/github.com/alena1108/cluster-controller/.gitignore b/vendor/github.com/alena1108/cluster-controller/.gitignore new file mode 100644 index 000000000..9e8d97d24 --- /dev/null +++ b/vendor/github.com/alena1108/cluster-controller/.gitignore @@ -0,0 +1,6 @@ +/.dapper +/bin +/dist +*.swp +/.trash-cache +cluster-controller diff --git a/vendor/github.com/alena1108/cluster-controller/Dockerfile.dapper b/vendor/github.com/alena1108/cluster-controller/Dockerfile.dapper new file mode 100644 index 000000000..b7ad8e2be --- /dev/null +++ b/vendor/github.com/alena1108/cluster-controller/Dockerfile.dapper @@ -0,0 +1,31 @@ +FROM ubuntu:16.04 +# FROM arm=armhf/ubuntu:16.04 + +ARG DAPPER_HOST_ARCH +ENV HOST_ARCH=${DAPPER_HOST_ARCH} ARCH=${DAPPER_HOST_ARCH} + +RUN apt-get update && \ + apt-get install -y gcc ca-certificates git wget curl vim less file && \ + rm -f /bin/sh && ln -s /bin/bash /bin/sh + +ENV GOLANG_ARCH_amd64=amd64 GOLANG_ARCH_arm=armv6l GOLANG_ARCH=GOLANG_ARCH_${ARCH} \ + GOPATH=/go PATH=/go/bin:/usr/local/go/bin:${PATH} SHELL=/bin/bash + +RUN wget -O - https://storage.googleapis.com/golang/go1.8.3.linux-${!GOLANG_ARCH}.tar.gz | tar -xzf - -C /usr/local && \ + go get github.com/rancher/trash && go get github.com/golang/lint/golint + +ENV DOCKER_URL_amd64=https://get.docker.com/builds/Linux/x86_64/docker-1.10.3 \ + DOCKER_URL_arm=https://github.com/rancher/docker/releases/download/v1.10.3-ros1/docker-1.10.3_arm \ + DOCKER_URL=DOCKER_URL_${ARCH} + +RUN wget -O - ${!DOCKER_URL} > /usr/bin/docker && chmod +x /usr/bin/docker + +ENV DAPPER_SOURCE /go/src/github.com/rancher/cluster-controller/ +ENV DAPPER_OUTPUT ./bin ./dist +ENV DAPPER_DOCKER_SOCKET true +ENV TRASH_CACHE ${DAPPER_SOURCE}/.trash-cache +ENV HOME ${DAPPER_SOURCE} +WORKDIR ${DAPPER_SOURCE} + +ENTRYPOINT ["./scripts/entry"] +CMD ["ci"] diff --git a/vendor/github.com/alena1108/cluster-controller/LICENSE b/vendor/github.com/alena1108/cluster-controller/LICENSE new file mode 100644 index 000000000..f433b1a53 --- /dev/null +++ b/vendor/github.com/alena1108/cluster-controller/LICENSE @@ -0,0 +1,177 @@ + + 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 diff --git a/vendor/github.com/alena1108/cluster-controller/Makefile b/vendor/github.com/alena1108/cluster-controller/Makefile new file mode 100644 index 000000000..d7d72a16d --- /dev/null +++ b/vendor/github.com/alena1108/cluster-controller/Makefile @@ -0,0 +1,23 @@ +TARGETS := $(shell ls scripts) + +.dapper: + @echo Downloading dapper + @curl -sL https://releases.rancher.com/dapper/latest/dapper-`uname -s`-`uname -m` > .dapper.tmp + @@chmod +x .dapper.tmp + @./.dapper.tmp -v + @mv .dapper.tmp .dapper + +$(TARGETS): .dapper + ./.dapper $@ + +trash: .dapper + ./.dapper -m bind trash + +trash-keep: .dapper + ./.dapper -m bind trash -k + +deps: trash + +.DEFAULT_GOAL := ci + +.PHONY: $(TARGETS) diff --git a/vendor/github.com/alena1108/cluster-controller/README.md b/vendor/github.com/alena1108/cluster-controller/README.md new file mode 100644 index 000000000..acf4aa781 --- /dev/null +++ b/vendor/github.com/alena1108/cluster-controller/README.md @@ -0,0 +1,28 @@ +cluster-controller +======== + +A microservice that does micro things. + +## Building + +`make` + + +## Running + +`./bin/cluster-controller` + +## License +Copyright (c) 2014-2017 [Rancher Labs, Inc.](http://rancher.com) + +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](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/alena1108/cluster-controller/client/v1/client.go b/vendor/github.com/alena1108/cluster-controller/client/v1/client.go new file mode 100644 index 000000000..726f5bed3 --- /dev/null +++ b/vendor/github.com/alena1108/cluster-controller/client/v1/client.go @@ -0,0 +1,63 @@ +package v1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/pkg/api" + "k8s.io/client-go/rest" +) + +const ( + Group = "rancher.com" +) + +var Version = "v1" + +type ClustersManagerV1Interface interface { + RESTClient() rest.Interface + ClustersGetter +} + +type ClustersManagerV1Client struct { + restClient rest.Interface + dynamicClient *dynamic.Client +} + +func (c *ClustersManagerV1Client) Clusters() ClusterInterface { + return newClusters(c.restClient, c.dynamicClient) +} + +func (c *ClustersManagerV1Client) ClusterNodes() ClusterNodeInterface { + return newClusterNodes(c.restClient, c.dynamicClient) +} + +func (c *ClustersManagerV1Client) RESTClient() rest.Interface { + return c.restClient +} + +func NewForConfig(c *rest.Config) (*ClustersManagerV1Client, error) { + config := *c + SetConfigDefaults(&config) + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + + dynamicClient, err := dynamic.NewClient(&config) + if err != nil { + return nil, err + } + + return &ClustersManagerV1Client{client, dynamicClient}, nil +} + +func SetConfigDefaults(config *rest.Config) { + config.GroupVersion = &schema.GroupVersion{ + Group: Group, + Version: Version, + } + config.APIPath = "/apis" + config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: api.Codecs} + return +} diff --git a/vendor/github.com/alena1108/cluster-controller/client/v1/cluster.go b/vendor/github.com/alena1108/cluster-controller/client/v1/cluster.go new file mode 100644 index 000000000..61016611f --- /dev/null +++ b/vendor/github.com/alena1108/cluster-controller/client/v1/cluster.go @@ -0,0 +1,184 @@ +package v1 + +import ( + "encoding/json" + + "github.com/pkg/errors" + 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/watch" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" +) + +const ( + ClustersKind = "Cluster" + ClustersName = "clusters" + ClustersSingularName = "cluster" +) + +type ClustersGetter interface { + Clusters(namespace string) ClusterInterface +} + +var _ ClusterInterface = &clusters{} + +type ClusterInterface interface { + Create(*Cluster) (*Cluster, error) + Get(name string, opts metav1.GetOptions) (*Cluster, error) + Update(*Cluster) (*Cluster, error) + Delete(name string, options *metav1.DeleteOptions) error + List(opts metav1.ListOptions) (runtime.Object, error) + Watch(opts metav1.ListOptions) (watch.Interface, error) + DeleteCollection(dopts *metav1.DeleteOptions, lopts metav1.ListOptions) error +} + +type clusters struct { + restClient rest.Interface + client *dynamic.ResourceClient + ns string +} + +func newClusters(r rest.Interface, c *dynamic.Client) *clusters { + return &clusters{ + r, + c.Resource( + &metav1.APIResource{ + Kind: ClustersKind, + Name: ClustersName, + Namespaced: false, + }, + "", + ), + "", + } +} + +func (p *clusters) Create(o *Cluster) (*Cluster, error) { + up, err := UnstructuredFromCluster(o) + if err != nil { + return nil, err + } + + up, err = p.client.Create(up) + if err != nil { + return nil, err + } + + return ClusterFromUnstructured(up) +} + +func (p *clusters) Get(name string, opts metav1.GetOptions) (*Cluster, error) { + obj, err := p.client.Get(name, opts) + if err != nil { + return nil, err + } + return ClusterFromUnstructured(obj) +} + +func (p *clusters) Update(o *Cluster) (*Cluster, error) { + up, err := UnstructuredFromCluster(o) + if err != nil { + return nil, err + } + + curp, err := p.Get(o.Name, metav1.GetOptions{}) + if err != nil { + return nil, errors.Wrap(err, "unable to get current version for update") + } + up.SetResourceVersion(curp.ObjectMeta.ResourceVersion) + + up, err = p.client.Update(up) + if err != nil { + return nil, err + } + + return ClusterFromUnstructured(up) +} + +func (p *clusters) Delete(name string, options *metav1.DeleteOptions) error { + return p.client.Delete(name, options) +} + +func (p *clusters) List(opts metav1.ListOptions) (runtime.Object, error) { + req := p.restClient.Get(). + Namespace(p.ns). + Resource(ClustersName). + FieldsSelectorParam(nil) + + b, err := req.DoRaw() + if err != nil { + return nil, err + } + var prom ClusterList + return &prom, json.Unmarshal(b, &prom) +} + +func (p *clusters) Watch(opts metav1.ListOptions) (watch.Interface, error) { + r, err := p.restClient.Get(). + Prefix("watch"). + Namespace(p.ns). + Resource(ClustersName). + FieldsSelectorParam(nil). + Stream() + if err != nil { + return nil, err + } + return watch.NewStreamWatcher(&clusterDecoder{ + dec: json.NewDecoder(r), + close: r.Close, + }), nil +} + +func (p *clusters) DeleteCollection(dopts *metav1.DeleteOptions, lopts metav1.ListOptions) error { + return p.client.DeleteCollection(dopts, lopts) +} + +func ClusterFromUnstructured(r *unstructured.Unstructured) (*Cluster, error) { + b, err := json.Marshal(r.Object) + if err != nil { + return nil, err + } + var p Cluster + if err := json.Unmarshal(b, &p); err != nil { + return nil, err + } + p.TypeMeta.Kind = ClustersKind + p.TypeMeta.APIVersion = Group + "/" + Version + return &p, nil +} + +func UnstructuredFromCluster(p *Cluster) (*unstructured.Unstructured, error) { + p.TypeMeta.Kind = ClustersKind + p.TypeMeta.APIVersion = Group + "/" + Version + b, err := json.Marshal(p) + if err != nil { + return nil, err + } + var r unstructured.Unstructured + if err := json.Unmarshal(b, &r.Object); err != nil { + return nil, err + } + return &r, nil +} + +type clusterDecoder struct { + dec *json.Decoder + close func() error +} + +func (d *clusterDecoder) Close() { + d.close() +} + +func (d *clusterDecoder) Decode() (action watch.EventType, object runtime.Object, err error) { + var e struct { + Type watch.EventType + Object Cluster + } + if err := d.dec.Decode(&e); err != nil { + return watch.Error, nil, err + } + return e.Type, &e.Object, nil +} diff --git a/vendor/github.com/alena1108/cluster-controller/client/v1/clusternode.go b/vendor/github.com/alena1108/cluster-controller/client/v1/clusternode.go new file mode 100644 index 000000000..5f33cd6e4 --- /dev/null +++ b/vendor/github.com/alena1108/cluster-controller/client/v1/clusternode.go @@ -0,0 +1,184 @@ +package v1 + +import ( + "encoding/json" + + "github.com/pkg/errors" + 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/watch" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" +) + +const ( + ClusterNodesKind = "ClusterNode" + ClusterNodesName = "clusternodes" + ClusterNodesSingularName = "clusternode" +) + +type ClusterNodesGetter interface { + ClusterNodes(namespace string) ClusterNodeInterface +} + +var _ ClusterNodeInterface = &clusternodes{} + +type ClusterNodeInterface interface { + Create(*ClusterNode) (*ClusterNode, error) + Get(name string, opts metav1.GetOptions) (*ClusterNode, error) + Update(*ClusterNode) (*ClusterNode, error) + Delete(name string, options *metav1.DeleteOptions) error + List(opts metav1.ListOptions) (runtime.Object, error) + Watch(opts metav1.ListOptions) (watch.Interface, error) + DeleteCollection(dopts *metav1.DeleteOptions, lopts metav1.ListOptions) error +} + +type clusternodes struct { + restClient rest.Interface + client *dynamic.ResourceClient + ns string +} + +func newClusterNodes(r rest.Interface, c *dynamic.Client) *clusternodes { + return &clusternodes{ + r, + c.Resource( + &metav1.APIResource{ + Kind: ClusterNodesKind, + Name: ClusterNodesName, + Namespaced: false, + }, + "", + ), + "", + } +} + +func (p *clusternodes) Create(o *ClusterNode) (*ClusterNode, error) { + up, err := UnstructuredFromClusterNode(o) + if err != nil { + return nil, err + } + + up, err = p.client.Create(up) + if err != nil { + return nil, err + } + + return ClusterNodeFromUnstructured(up) +} + +func (p *clusternodes) Get(name string, opts metav1.GetOptions) (*ClusterNode, error) { + obj, err := p.client.Get(name, opts) + if err != nil { + return nil, err + } + return ClusterNodeFromUnstructured(obj) +} + +func (p *clusternodes) Update(o *ClusterNode) (*ClusterNode, error) { + up, err := UnstructuredFromClusterNode(o) + if err != nil { + return nil, err + } + + curp, err := p.Get(o.Name, metav1.GetOptions{}) + if err != nil { + return nil, errors.Wrap(err, "unable to get current version for update") + } + up.SetResourceVersion(curp.ObjectMeta.ResourceVersion) + + up, err = p.client.Update(up) + if err != nil { + return nil, err + } + + return ClusterNodeFromUnstructured(up) +} + +func (p *clusternodes) Delete(name string, options *metav1.DeleteOptions) error { + return p.client.Delete(name, options) +} + +func (p *clusternodes) List(opts metav1.ListOptions) (runtime.Object, error) { + req := p.restClient.Get(). + Namespace(p.ns). + Resource(ClusterNodesName). + FieldsSelectorParam(nil) + + b, err := req.DoRaw() + if err != nil { + return nil, err + } + var prom ClusterNodeList + return &prom, json.Unmarshal(b, &prom) +} + +func (p *clusternodes) Watch(opts metav1.ListOptions) (watch.Interface, error) { + r, err := p.restClient.Get(). + Prefix("watch"). + Namespace(p.ns). + Resource(ClusterNodesName). + FieldsSelectorParam(nil). + Stream() + if err != nil { + return nil, err + } + return watch.NewStreamWatcher(&clusterDecoder{ + dec: json.NewDecoder(r), + close: r.Close, + }), nil +} + +func (p *clusternodes) DeleteCollection(dopts *metav1.DeleteOptions, lopts metav1.ListOptions) error { + return p.client.DeleteCollection(dopts, lopts) +} + +func ClusterNodeFromUnstructured(r *unstructured.Unstructured) (*ClusterNode, error) { + b, err := json.Marshal(r.Object) + if err != nil { + return nil, err + } + var p ClusterNode + if err := json.Unmarshal(b, &p); err != nil { + return nil, err + } + p.TypeMeta.Kind = ClusterNodesKind + p.TypeMeta.APIVersion = Group + "/" + Version + return &p, nil +} + +func UnstructuredFromClusterNode(p *ClusterNode) (*unstructured.Unstructured, error) { + p.TypeMeta.Kind = ClusterNodesKind + p.TypeMeta.APIVersion = Group + "/" + Version + b, err := json.Marshal(p) + if err != nil { + return nil, err + } + var r unstructured.Unstructured + if err := json.Unmarshal(b, &r.Object); err != nil { + return nil, err + } + return &r, nil +} + +type clusterNodeDecoder struct { + dec *json.Decoder + close func() error +} + +func (d *clusterNodeDecoder) Close() { + d.close() +} + +func (d *clusterNodeDecoder) Decode() (action watch.EventType, object runtime.Object, err error) { + var e struct { + Type watch.EventType + Object ClusterNode + } + if err := d.dec.Decode(&e); err != nil { + return watch.Error, nil, err + } + return e.Type, &e.Object, nil +} diff --git a/vendor/github.com/alena1108/cluster-controller/client/v1/types.go b/vendor/github.com/alena1108/cluster-controller/client/v1/types.go new file mode 100644 index 000000000..4b62a76ac --- /dev/null +++ b/vendor/github.com/alena1108/cluster-controller/client/v1/types.go @@ -0,0 +1,210 @@ +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/pkg/api/v1" +) + +type ClusterConditionType string + +const ( + // ClusterConditionReady Cluster ready to serve API (healthy when true, unehalthy when false) + ClusterConditionReady = "Ready" + // ClusterConditionProvisioned Cluster is provisioned + ClusterConditionProvisioned = "Provisioned" + // ClusterConditionUpdating Cluster is being updating (upgrading, scaling up) + ClusterConditionUpdating = "Updating" + // More conditions can be added if unredlying controllers request it +) + +type Cluster struct { + metav1.TypeMeta `json:",inline"` + // Standard object’s metadata. More info: + // https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata + metav1.ObjectMeta `json:"metadata,omitempty"` + // Specification of the desired behavior of the the cluster. More info: + // https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status + Spec ClusterSpec `json:"spec"` + // Most recent observed status of the cluster. More info: + // https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status + Status *ClusterStatus `json:"status"` +} + +type ClusterList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata + // More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata + metav1.ListMeta `json:"metadata,omitempty"` + // List of Clusters + Items []*Cluster `json:"items"` +} + +type ClusterSpec struct { + GKEConfig *GKEConfig + AKSConfig *AKSConfig + RKEConfig *RKEConfig +} + +type ClusterStatus struct { + //Conditions represent the latest available observations of an object's current state: + //More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#typical-status-properties + Conditions []ClusterCondition `json:"conditions,omitempty"` + //Component statuses will represent cluster's components (etcd/controller/scheduler) health + // https://kubernetes.io/docs/api-reference/v1.8/#componentstatus-v1-core + ComponentStatuses v1.ComponentStatusList + APIEndpoint string + ServiceAccountToken string + CACert string +} + +type ClusterCondition struct { + // Type of cluster condition. + Type ClusterConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status v1.ConditionStatus `json:"status"` + // The last time this condition was updated. + LastUpdateTime string `json:"lastUpdateTime,omitempty"` + // Last time the condition transitioned from one status to another. + LastTransitionTime string `json:"lastTransitionTime,omitempty"` + // The reason for the condition's last transition. + Reason string `json:"reason,omitempty"` +} + +type GKEConfig struct { + // ProjectID is the ID of your project to use when creating a cluster + ProjectID string `json:"projectId,omitempty"` + // The zone to launch the cluster + Zone string `json:"zone,omitempty"` + // The IP address range of the container pods + ClusterIpv4Cidr string `json:"clusterIpv4Cidr,omitempty"` + // An optional description of this cluster + Description string `json:"description,omitempty"` + // The number of nodes to create in this cluster + InitialNodeCount int64 `json:"initialNodeCount,omitempty"` + // Size of the disk attached to each node + DiskSizeGb int64 `json:"diskSizeGb,omitempty"` + // The name of a Google Compute Engine + MachineType string `json:"machineType,omitempty"` + // the initial kubernetes version + InitialClusterVersion string `json:"initialClusterVersion,omitempty"` + // The map of Kubernetes labels (key/value pairs) to be applied + // to each node. + Labels map[string]string `json:"labels,omitempty"` + // The path to the credential file(key.json) + CredentialPath string `json:"credentialPath,omitempty"` + // Enable alpha feature + EnableAlphaFeature bool `json:"enableAlphaFeature,omitempty"` + // NodePool id + NodePoolID string `json:"nodePoolID,omitempty"` + + // Update Config + UpdateConfig gkeUpdateConfig `json:"updateConfig,omitempty"` +} + +type gkeUpdateConfig struct { + // the number of node + NodeCount int64 `json:"nodeCount,omitempty"` + // Master kubernetes version + MasterVersion string `json:"masterVersion,omitempty"` + // Node kubernetes version + NodeVersion string `json:"nodeVersion,omitempty"` +} + +type AKSConfig struct { + //TBD +} + +type RKEConfig struct { + // Kubernetes nodes + Hosts []RKEConfigHost `yaml:"hosts"` + // Kubernetes components + Services RKEConfigServices `yaml:"services"` +} + +type RKEConfigHost struct { + // SSH IP address of the host + IP string `yaml:"ip"` + // Advertised address that will be used for components communication + AdvertiseAddress string `yaml:"advertise_address"` + // Host role in kubernetes cluster (controlplane, worker, or etcd) + Role []string `yaml:"role"` + // Hostname of the host + Hostname string `yaml:"hostname"` + // SSH usesr that will be used by RKE + User string `yaml:"user"` + // Docker socket on the host that will be used in tunneling + DockerSocket string `yaml:"docker_socket"` +} + +type RKEConfigServices struct { + // Etcd Service + Etcd ETCDService `yaml:"etcd"` + // KubeAPI Service + KubeAPI KubeAPIService `yaml:"kube-api"` + // KubeController Service + KubeController KubeControllerService `yaml:"kube-controller"` + // Scheduler Service + Scheduler SchedulerService `yaml:"scheduler"` + // Kubelet Service + Kubelet KubeletService `yaml:"kubelet"` + // KubeProxy Service + Kubeproxy KubeproxyService `yaml:"kubeproxy"` +} + +type ETCDService struct { + // Base service properties + baseService +} + +type KubeAPIService struct { + // Base service properties + baseService + // Virtual IP range that will be used by Kubernetes services + ServiceClusterIPRange string `yaml:"service_cluster_ip_range"` +} + +type KubeControllerService struct { + // Base service properties + baseService + // CIDR Range for Pods in cluster + ClusterCIDR string `yaml:"cluster_cidr"` + // Virtual IP range that will be used by Kubernetes services + ServiceClusterIPRange string `yaml:"service_cluster_ip_range"` +} + +type KubeletService struct { + // Base service properties + baseService + // Domain of the cluster (default: "cluster.local") + ClusterDomain string `yaml:"cluster_domain"` + // The image whose network/ipc namespaces containers in each pod will use + InfraContainerImage string `yaml:"infra_container_image"` +} + +type KubeproxyService struct { + // Base service properties + baseService +} + +type SchedulerService struct { + // Base service properties + baseService +} + +type baseService struct { + // Docker image of the service + Image string `yaml:"image"` +} + +type ClusterNode struct { + v1.Node +} + +type ClusterNodeList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata + // More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata + metav1.ListMeta `json:"metadata,omitempty"` + // List of Clusters + Items []*Cluster `json:"items"` +} diff --git a/vendor/github.com/alena1108/cluster-controller/vendor.conf b/vendor/github.com/alena1108/cluster-controller/vendor.conf new file mode 100644 index 000000000..4ef86ebff --- /dev/null +++ b/vendor/github.com/alena1108/cluster-controller/vendor.conf @@ -0,0 +1,10 @@ +# package +github.com/rancher/cluster-controller + +github.com/Sirupsen/logrus v0.10.0 +github.com/urfave/cli v1.18.0 +k8s.io/client-go v4.0.0 transitive=true +github.com/pkg/errors v0.8.0 +github.com/urfave/cli v1.19.1 +golang.org/x/sync/errgroup 450f422ab23cf9881c94e2db30cac0eb1b7cf80c + diff --git a/vendor/github.com/pkg/errors/.gitignore b/vendor/github.com/pkg/errors/.gitignore new file mode 100644 index 000000000..daf913b1b --- /dev/null +++ b/vendor/github.com/pkg/errors/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml new file mode 100644 index 000000000..7ca408d1b --- /dev/null +++ b/vendor/github.com/pkg/errors/.travis.yml @@ -0,0 +1,13 @@ +language: go +go_import_path: github.com/pkg/errors +go: + - 1.4.x + - 1.5.x + - 1.6.x + - 1.7.x + - 1.8.x + - 1.9.x + - tip + +script: + - go test -v ./... diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 000000000..835ba3e75 --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md new file mode 100644 index 000000000..273db3c98 --- /dev/null +++ b/vendor/github.com/pkg/errors/README.md @@ -0,0 +1,52 @@ +# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) + +Package errors provides simple error handling primitives. + +`go get github.com/pkg/errors` + +The traditional error handling idiom in Go is roughly akin to +```go +if err != nil { + return err +} +``` +which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. + +## Adding context to an error + +The errors.Wrap function returns a new error that adds context to the original error. For example +```go +_, err := ioutil.ReadAll(r) +if err != nil { + return errors.Wrap(err, "read failed") +} +``` +## Retrieving the cause of an error + +Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. +```go +type causer interface { + Cause() error +} +``` +`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: +```go +switch err := errors.Cause(err).(type) { +case *MyError: + // handle specifically +default: + // unknown error +} +``` + +[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). + +## Contributing + +We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. + +Before proposing a change, please discuss your change by raising an issue. + +## Licence + +BSD-2-Clause diff --git a/vendor/github.com/pkg/errors/appveyor.yml b/vendor/github.com/pkg/errors/appveyor.yml new file mode 100644 index 000000000..a932eade0 --- /dev/null +++ b/vendor/github.com/pkg/errors/appveyor.yml @@ -0,0 +1,32 @@ +version: build-{build}.{branch} + +clone_folder: C:\gopath\src\github.com\pkg\errors +shallow_clone: true # for startup speed + +environment: + GOPATH: C:\gopath + +platform: + - x64 + +# http://www.appveyor.com/docs/installed-software +install: + # some helpful output for debugging builds + - go version + - go env + # pre-installed MinGW at C:\MinGW is 32bit only + # but MSYS2 at C:\msys64 has mingw64 + - set PATH=C:\msys64\mingw64\bin;%PATH% + - gcc --version + - g++ --version + +build_script: + - go install -v ./... + +test_script: + - set PATH=C:\gopath\bin;%PATH% + - go test -v ./... + +#artifacts: +# - path: '%GOPATH%\bin\*.exe' +deploy: off diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 000000000..842ee8045 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,269 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, +// and the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// If additional control is required the errors.WithStack and errors.WithMessage +// functions destructure errors.Wrap into its component operations of annotating +// an error with a stack trace and an a message, respectively. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface. +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func WithStack(err error) error { + if err == nil { + return nil + } + return &withStack{ + err, + callers(), + } +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is call, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go new file mode 100644 index 000000000..cbe3f3e38 --- /dev/null +++ b/vendor/github.com/pkg/errors/stack.go @@ -0,0 +1,186 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strings" +) + +// Frame represents a program counter inside a stack frame. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s path of source file relative to the compile time GOPATH +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(s, "unknown") + } else { + file, _ := fn.FileLine(pc) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + } + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + fmt.Fprintf(s, "%d", f.line()) + case 'n': + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(s, funcname(name)) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +// Format formats the stack of Frames according to the fmt.Formatter interface. +// +// %s lists source files for each Frame in the stack +// %v lists the source file and line number for each Frame in the stack +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+v Prints filename, function, and line number for each Frame in the stack. +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + fmt.Fprintf(s, "%v", []Frame(st)) + } + case 's': + fmt.Fprintf(s, "%s", []Frame(st)) + } +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} + +func trimGOPATH(name, file string) string { + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(name, sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + return file +} diff --git a/vendor/gopkg.in/check.v1/.gitignore b/vendor/gopkg.in/check.v1/.gitignore new file mode 100644 index 000000000..191a5360b --- /dev/null +++ b/vendor/gopkg.in/check.v1/.gitignore @@ -0,0 +1,4 @@ +_* +*.swp +*.[568] +[568].out diff --git a/vendor/gopkg.in/check.v1/.travis.yml b/vendor/gopkg.in/check.v1/.travis.yml new file mode 100644 index 000000000..ead6735fc --- /dev/null +++ b/vendor/gopkg.in/check.v1/.travis.yml @@ -0,0 +1,3 @@ +language: go + +go_import_path: gopkg.in/check.v1 diff --git a/vendor/gopkg.in/check.v1/LICENSE b/vendor/gopkg.in/check.v1/LICENSE new file mode 100644 index 000000000..545cf2d33 --- /dev/null +++ b/vendor/gopkg.in/check.v1/LICENSE @@ -0,0 +1,25 @@ +Gocheck - A rich testing framework for Go + +Copyright (c) 2010-2013 Gustavo Niemeyer + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/gopkg.in/check.v1/README.md b/vendor/gopkg.in/check.v1/README.md new file mode 100644 index 000000000..0ca9e5726 --- /dev/null +++ b/vendor/gopkg.in/check.v1/README.md @@ -0,0 +1,20 @@ +Instructions +============ + +Install the package with: + + go get gopkg.in/check.v1 + +Import it with: + + import "gopkg.in/check.v1" + +and use _check_ as the package name inside the code. + +For more details, visit the project page: + +* http://labix.org/gocheck + +and the API documentation: + +* https://gopkg.in/check.v1 diff --git a/vendor/gopkg.in/check.v1/TODO b/vendor/gopkg.in/check.v1/TODO new file mode 100644 index 000000000..33498270e --- /dev/null +++ b/vendor/gopkg.in/check.v1/TODO @@ -0,0 +1,2 @@ +- Assert(slice, Contains, item) +- Parallel test support diff --git a/vendor/gopkg.in/check.v1/benchmark.go b/vendor/gopkg.in/check.v1/benchmark.go new file mode 100644 index 000000000..46ea9dc6d --- /dev/null +++ b/vendor/gopkg.in/check.v1/benchmark.go @@ -0,0 +1,187 @@ +// Copyright (c) 2012 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package check + +import ( + "fmt" + "runtime" + "time" +) + +var memStats runtime.MemStats + +// testingB is a type passed to Benchmark functions to manage benchmark +// timing and to specify the number of iterations to run. +type timer struct { + start time.Time // Time test or benchmark started + duration time.Duration + N int + bytes int64 + timerOn bool + benchTime time.Duration + // The initial states of memStats.Mallocs and memStats.TotalAlloc. + startAllocs uint64 + startBytes uint64 + // The net total of this test after being run. + netAllocs uint64 + netBytes uint64 +} + +// StartTimer starts timing a test. This function is called automatically +// before a benchmark starts, but it can also used to resume timing after +// a call to StopTimer. +func (c *C) StartTimer() { + if !c.timerOn { + c.start = time.Now() + c.timerOn = true + + runtime.ReadMemStats(&memStats) + c.startAllocs = memStats.Mallocs + c.startBytes = memStats.TotalAlloc + } +} + +// StopTimer stops timing a test. This can be used to pause the timer +// while performing complex initialization that you don't +// want to measure. +func (c *C) StopTimer() { + if c.timerOn { + c.duration += time.Now().Sub(c.start) + c.timerOn = false + runtime.ReadMemStats(&memStats) + c.netAllocs += memStats.Mallocs - c.startAllocs + c.netBytes += memStats.TotalAlloc - c.startBytes + } +} + +// ResetTimer sets the elapsed benchmark time to zero. +// It does not affect whether the timer is running. +func (c *C) ResetTimer() { + if c.timerOn { + c.start = time.Now() + runtime.ReadMemStats(&memStats) + c.startAllocs = memStats.Mallocs + c.startBytes = memStats.TotalAlloc + } + c.duration = 0 + c.netAllocs = 0 + c.netBytes = 0 +} + +// SetBytes informs the number of bytes that the benchmark processes +// on each iteration. If this is called in a benchmark it will also +// report MB/s. +func (c *C) SetBytes(n int64) { + c.bytes = n +} + +func (c *C) nsPerOp() int64 { + if c.N <= 0 { + return 0 + } + return c.duration.Nanoseconds() / int64(c.N) +} + +func (c *C) mbPerSec() float64 { + if c.bytes <= 0 || c.duration <= 0 || c.N <= 0 { + return 0 + } + return (float64(c.bytes) * float64(c.N) / 1e6) / c.duration.Seconds() +} + +func (c *C) timerString() string { + if c.N <= 0 { + return fmt.Sprintf("%3.3fs", float64(c.duration.Nanoseconds())/1e9) + } + mbs := c.mbPerSec() + mb := "" + if mbs != 0 { + mb = fmt.Sprintf("\t%7.2f MB/s", mbs) + } + nsop := c.nsPerOp() + ns := fmt.Sprintf("%10d ns/op", nsop) + if c.N > 0 && nsop < 100 { + // The format specifiers here make sure that + // the ones digits line up for all three possible formats. + if nsop < 10 { + ns = fmt.Sprintf("%13.2f ns/op", float64(c.duration.Nanoseconds())/float64(c.N)) + } else { + ns = fmt.Sprintf("%12.1f ns/op", float64(c.duration.Nanoseconds())/float64(c.N)) + } + } + memStats := "" + if c.benchMem { + allocedBytes := fmt.Sprintf("%8d B/op", int64(c.netBytes)/int64(c.N)) + allocs := fmt.Sprintf("%8d allocs/op", int64(c.netAllocs)/int64(c.N)) + memStats = fmt.Sprintf("\t%s\t%s", allocedBytes, allocs) + } + return fmt.Sprintf("%8d\t%s%s%s", c.N, ns, mb, memStats) +} + +func min(x, y int) int { + if x > y { + return y + } + return x +} + +func max(x, y int) int { + if x < y { + return y + } + return x +} + +// roundDown10 rounds a number down to the nearest power of 10. +func roundDown10(n int) int { + var tens = 0 + // tens = floor(log_10(n)) + for n > 10 { + n = n / 10 + tens++ + } + // result = 10^tens + result := 1 + for i := 0; i < tens; i++ { + result *= 10 + } + return result +} + +// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX]. +func roundUp(n int) int { + base := roundDown10(n) + if n < (2 * base) { + return 2 * base + } + if n < (5 * base) { + return 5 * base + } + return 10 * base +} diff --git a/vendor/gopkg.in/check.v1/check.go b/vendor/gopkg.in/check.v1/check.go new file mode 100644 index 000000000..137a2749a --- /dev/null +++ b/vendor/gopkg.in/check.v1/check.go @@ -0,0 +1,873 @@ +// Package check is a rich testing extension for Go's testing package. +// +// For details about the project, see: +// +// http://labix.org/gocheck +// +package check + +import ( + "bytes" + "errors" + "fmt" + "io" + "math/rand" + "os" + "path" + "path/filepath" + "reflect" + "regexp" + "runtime" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" +) + +// ----------------------------------------------------------------------- +// Internal type which deals with suite method calling. + +const ( + fixtureKd = iota + testKd +) + +type funcKind int + +const ( + succeededSt = iota + failedSt + skippedSt + panickedSt + fixturePanickedSt + missedSt +) + +type funcStatus uint32 + +// A method value can't reach its own Method structure. +type methodType struct { + reflect.Value + Info reflect.Method +} + +func newMethod(receiver reflect.Value, i int) *methodType { + return &methodType{receiver.Method(i), receiver.Type().Method(i)} +} + +func (method *methodType) PC() uintptr { + return method.Info.Func.Pointer() +} + +func (method *methodType) suiteName() string { + t := method.Info.Type.In(0) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t.Name() +} + +func (method *methodType) String() string { + return method.suiteName() + "." + method.Info.Name +} + +func (method *methodType) matches(re *regexp.Regexp) bool { + return (re.MatchString(method.Info.Name) || + re.MatchString(method.suiteName()) || + re.MatchString(method.String())) +} + +type C struct { + method *methodType + kind funcKind + testName string + _status funcStatus + logb *logger + logw io.Writer + done chan *C + reason string + mustFail bool + tempDir *tempDir + benchMem bool + startTime time.Time + timer +} + +func (c *C) status() funcStatus { + return funcStatus(atomic.LoadUint32((*uint32)(&c._status))) +} + +func (c *C) setStatus(s funcStatus) { + atomic.StoreUint32((*uint32)(&c._status), uint32(s)) +} + +func (c *C) stopNow() { + runtime.Goexit() +} + +// logger is a concurrency safe byte.Buffer +type logger struct { + sync.Mutex + writer bytes.Buffer +} + +func (l *logger) Write(buf []byte) (int, error) { + l.Lock() + defer l.Unlock() + return l.writer.Write(buf) +} + +func (l *logger) WriteTo(w io.Writer) (int64, error) { + l.Lock() + defer l.Unlock() + return l.writer.WriteTo(w) +} + +func (l *logger) String() string { + l.Lock() + defer l.Unlock() + return l.writer.String() +} + +// ----------------------------------------------------------------------- +// Handling of temporary files and directories. + +type tempDir struct { + sync.Mutex + path string + counter int +} + +func (td *tempDir) newPath() string { + td.Lock() + defer td.Unlock() + if td.path == "" { + var err error + for i := 0; i != 100; i++ { + path := fmt.Sprintf("%s%ccheck-%d", os.TempDir(), os.PathSeparator, rand.Int()) + if err = os.Mkdir(path, 0700); err == nil { + td.path = path + break + } + } + if td.path == "" { + panic("Couldn't create temporary directory: " + err.Error()) + } + } + result := filepath.Join(td.path, strconv.Itoa(td.counter)) + td.counter++ + return result +} + +func (td *tempDir) removeAll() { + td.Lock() + defer td.Unlock() + if td.path != "" { + err := os.RemoveAll(td.path) + if err != nil { + fmt.Fprintf(os.Stderr, "WARNING: Error cleaning up temporaries: "+err.Error()) + } + } +} + +// Create a new temporary directory which is automatically removed after +// the suite finishes running. +func (c *C) MkDir() string { + path := c.tempDir.newPath() + if err := os.Mkdir(path, 0700); err != nil { + panic(fmt.Sprintf("Couldn't create temporary directory %s: %s", path, err.Error())) + } + return path +} + +// ----------------------------------------------------------------------- +// Low-level logging functions. + +func (c *C) log(args ...interface{}) { + c.writeLog([]byte(fmt.Sprint(args...) + "\n")) +} + +func (c *C) logf(format string, args ...interface{}) { + c.writeLog([]byte(fmt.Sprintf(format+"\n", args...))) +} + +func (c *C) logNewLine() { + c.writeLog([]byte{'\n'}) +} + +func (c *C) writeLog(buf []byte) { + c.logb.Write(buf) + if c.logw != nil { + c.logw.Write(buf) + } +} + +func hasStringOrError(x interface{}) (ok bool) { + _, ok = x.(fmt.Stringer) + if ok { + return + } + _, ok = x.(error) + return +} + +func (c *C) logValue(label string, value interface{}) { + if label == "" { + if hasStringOrError(value) { + c.logf("... %#v (%q)", value, value) + } else { + c.logf("... %#v", value) + } + } else if value == nil { + c.logf("... %s = nil", label) + } else { + if hasStringOrError(value) { + fv := fmt.Sprintf("%#v", value) + qv := fmt.Sprintf("%q", value) + if fv != qv { + c.logf("... %s %s = %s (%s)", label, reflect.TypeOf(value), fv, qv) + return + } + } + if s, ok := value.(string); ok && isMultiLine(s) { + c.logf(`... %s %s = "" +`, label, reflect.TypeOf(value)) + c.logMultiLine(s) + } else { + c.logf("... %s %s = %#v", label, reflect.TypeOf(value), value) + } + } +} + +func (c *C) logMultiLine(s string) { + b := make([]byte, 0, len(s)*2) + i := 0 + n := len(s) + for i < n { + j := i + 1 + for j < n && s[j-1] != '\n' { + j++ + } + b = append(b, "... "...) + b = strconv.AppendQuote(b, s[i:j]) + if j < n { + b = append(b, " +"...) + } + b = append(b, '\n') + i = j + } + c.writeLog(b) +} + +func isMultiLine(s string) bool { + for i := 0; i+1 < len(s); i++ { + if s[i] == '\n' { + return true + } + } + return false +} + +func (c *C) logString(issue string) { + c.log("... ", issue) +} + +func (c *C) logCaller(skip int) { + // This is a bit heavier than it ought to be. + skip++ // Our own frame. + pc, callerFile, callerLine, ok := runtime.Caller(skip) + if !ok { + return + } + var testFile string + var testLine int + testFunc := runtime.FuncForPC(c.method.PC()) + if runtime.FuncForPC(pc) != testFunc { + for { + skip++ + if pc, file, line, ok := runtime.Caller(skip); ok { + // Note that the test line may be different on + // distinct calls for the same test. Showing + // the "internal" line is helpful when debugging. + if runtime.FuncForPC(pc) == testFunc { + testFile, testLine = file, line + break + } + } else { + break + } + } + } + if testFile != "" && (testFile != callerFile || testLine != callerLine) { + c.logCode(testFile, testLine) + } + c.logCode(callerFile, callerLine) +} + +func (c *C) logCode(path string, line int) { + c.logf("%s:%d:", nicePath(path), line) + code, err := printLine(path, line) + if code == "" { + code = "..." // XXX Open the file and take the raw line. + if err != nil { + code += err.Error() + } + } + c.log(indent(code, " ")) +} + +var valueGo = filepath.Join("reflect", "value.go") +var asmGo = filepath.Join("runtime", "asm_") + +func (c *C) logPanic(skip int, value interface{}) { + skip++ // Our own frame. + initialSkip := skip + for ; ; skip++ { + if pc, file, line, ok := runtime.Caller(skip); ok { + if skip == initialSkip { + c.logf("... Panic: %s (PC=0x%X)\n", value, pc) + } + name := niceFuncName(pc) + path := nicePath(file) + if strings.Contains(path, "/gopkg.in/check.v") { + continue + } + if name == "Value.call" && strings.HasSuffix(path, valueGo) { + continue + } + if (name == "call16" || name == "call32") && strings.Contains(path, asmGo) { + continue + } + c.logf("%s:%d\n in %s", nicePath(file), line, name) + } else { + break + } + } +} + +func (c *C) logSoftPanic(issue string) { + c.log("... Panic: ", issue) +} + +func (c *C) logArgPanic(method *methodType, expectedType string) { + c.logf("... Panic: %s argument should be %s", + niceFuncName(method.PC()), expectedType) +} + +// ----------------------------------------------------------------------- +// Some simple formatting helpers. + +var initWD, initWDErr = os.Getwd() + +func init() { + if initWDErr == nil { + initWD = strings.Replace(initWD, "\\", "/", -1) + "/" + } +} + +func nicePath(path string) string { + if initWDErr == nil { + if strings.HasPrefix(path, initWD) { + return path[len(initWD):] + } + } + return path +} + +func niceFuncPath(pc uintptr) string { + function := runtime.FuncForPC(pc) + if function != nil { + filename, line := function.FileLine(pc) + return fmt.Sprintf("%s:%d", nicePath(filename), line) + } + return "" +} + +func niceFuncName(pc uintptr) string { + function := runtime.FuncForPC(pc) + if function != nil { + name := path.Base(function.Name()) + if i := strings.Index(name, "."); i > 0 { + name = name[i+1:] + } + if strings.HasPrefix(name, "(*") { + if i := strings.Index(name, ")"); i > 0 { + name = name[2:i] + name[i+1:] + } + } + if i := strings.LastIndex(name, ".*"); i != -1 { + name = name[:i] + "." + name[i+2:] + } + if i := strings.LastIndex(name, "·"); i != -1 { + name = name[:i] + "." + name[i+2:] + } + return name + } + return "" +} + +// ----------------------------------------------------------------------- +// Result tracker to aggregate call results. + +type Result struct { + Succeeded int + Failed int + Skipped int + Panicked int + FixturePanicked int + ExpectedFailures int + Missed int // Not even tried to run, related to a panic in the fixture. + RunError error // Houston, we've got a problem. + WorkDir string // If KeepWorkDir is true +} + +type resultTracker struct { + result Result + _lastWasProblem bool + _waiting int + _missed int + _expectChan chan *C + _doneChan chan *C + _stopChan chan bool +} + +func newResultTracker() *resultTracker { + return &resultTracker{_expectChan: make(chan *C), // Synchronous + _doneChan: make(chan *C, 32), // Asynchronous + _stopChan: make(chan bool)} // Synchronous +} + +func (tracker *resultTracker) start() { + go tracker._loopRoutine() +} + +func (tracker *resultTracker) waitAndStop() { + <-tracker._stopChan +} + +func (tracker *resultTracker) expectCall(c *C) { + tracker._expectChan <- c +} + +func (tracker *resultTracker) callDone(c *C) { + tracker._doneChan <- c +} + +func (tracker *resultTracker) _loopRoutine() { + for { + var c *C + if tracker._waiting > 0 { + // Calls still running. Can't stop. + select { + // XXX Reindent this (not now to make diff clear) + case <-tracker._expectChan: + tracker._waiting++ + case c = <-tracker._doneChan: + tracker._waiting-- + switch c.status() { + case succeededSt: + if c.kind == testKd { + if c.mustFail { + tracker.result.ExpectedFailures++ + } else { + tracker.result.Succeeded++ + } + } + case failedSt: + tracker.result.Failed++ + case panickedSt: + if c.kind == fixtureKd { + tracker.result.FixturePanicked++ + } else { + tracker.result.Panicked++ + } + case fixturePanickedSt: + // Track it as missed, since the panic + // was on the fixture, not on the test. + tracker.result.Missed++ + case missedSt: + tracker.result.Missed++ + case skippedSt: + if c.kind == testKd { + tracker.result.Skipped++ + } + } + } + } else { + // No calls. Can stop, but no done calls here. + select { + case tracker._stopChan <- true: + return + case <-tracker._expectChan: + tracker._waiting++ + case <-tracker._doneChan: + panic("Tracker got an unexpected done call.") + } + } + } +} + +// ----------------------------------------------------------------------- +// The underlying suite runner. + +type suiteRunner struct { + suite interface{} + setUpSuite, tearDownSuite *methodType + setUpTest, tearDownTest *methodType + tests []*methodType + tracker *resultTracker + tempDir *tempDir + keepDir bool + output *outputWriter + reportedProblemLast bool + benchTime time.Duration + benchMem bool +} + +type RunConf struct { + Output io.Writer + Stream bool + Verbose bool + Filter string + Benchmark bool + BenchmarkTime time.Duration // Defaults to 1 second + BenchmarkMem bool + KeepWorkDir bool +} + +// Create a new suiteRunner able to run all methods in the given suite. +func newSuiteRunner(suite interface{}, runConf *RunConf) *suiteRunner { + var conf RunConf + if runConf != nil { + conf = *runConf + } + if conf.Output == nil { + conf.Output = os.Stdout + } + if conf.Benchmark { + conf.Verbose = true + } + + suiteType := reflect.TypeOf(suite) + suiteNumMethods := suiteType.NumMethod() + suiteValue := reflect.ValueOf(suite) + + runner := &suiteRunner{ + suite: suite, + output: newOutputWriter(conf.Output, conf.Stream, conf.Verbose), + tracker: newResultTracker(), + benchTime: conf.BenchmarkTime, + benchMem: conf.BenchmarkMem, + tempDir: &tempDir{}, + keepDir: conf.KeepWorkDir, + tests: make([]*methodType, 0, suiteNumMethods), + } + if runner.benchTime == 0 { + runner.benchTime = 1 * time.Second + } + + var filterRegexp *regexp.Regexp + if conf.Filter != "" { + regexp, err := regexp.Compile(conf.Filter) + if err != nil { + msg := "Bad filter expression: " + err.Error() + runner.tracker.result.RunError = errors.New(msg) + return runner + } + filterRegexp = regexp + } + + for i := 0; i != suiteNumMethods; i++ { + method := newMethod(suiteValue, i) + switch method.Info.Name { + case "SetUpSuite": + runner.setUpSuite = method + case "TearDownSuite": + runner.tearDownSuite = method + case "SetUpTest": + runner.setUpTest = method + case "TearDownTest": + runner.tearDownTest = method + default: + prefix := "Test" + if conf.Benchmark { + prefix = "Benchmark" + } + if !strings.HasPrefix(method.Info.Name, prefix) { + continue + } + if filterRegexp == nil || method.matches(filterRegexp) { + runner.tests = append(runner.tests, method) + } + } + } + return runner +} + +// Run all methods in the given suite. +func (runner *suiteRunner) run() *Result { + if runner.tracker.result.RunError == nil && len(runner.tests) > 0 { + runner.tracker.start() + if runner.checkFixtureArgs() { + c := runner.runFixture(runner.setUpSuite, "", nil) + if c == nil || c.status() == succeededSt { + for i := 0; i != len(runner.tests); i++ { + c := runner.runTest(runner.tests[i]) + if c.status() == fixturePanickedSt { + runner.skipTests(missedSt, runner.tests[i+1:]) + break + } + } + } else if c != nil && c.status() == skippedSt { + runner.skipTests(skippedSt, runner.tests) + } else { + runner.skipTests(missedSt, runner.tests) + } + runner.runFixture(runner.tearDownSuite, "", nil) + } else { + runner.skipTests(missedSt, runner.tests) + } + runner.tracker.waitAndStop() + if runner.keepDir { + runner.tracker.result.WorkDir = runner.tempDir.path + } else { + runner.tempDir.removeAll() + } + } + return &runner.tracker.result +} + +// Create a call object with the given suite method, and fork a +// goroutine with the provided dispatcher for running it. +func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C { + var logw io.Writer + if runner.output.Stream { + logw = runner.output + } + if logb == nil { + logb = new(logger) + } + c := &C{ + method: method, + kind: kind, + testName: testName, + logb: logb, + logw: logw, + tempDir: runner.tempDir, + done: make(chan *C, 1), + timer: timer{benchTime: runner.benchTime}, + startTime: time.Now(), + benchMem: runner.benchMem, + } + runner.tracker.expectCall(c) + go (func() { + runner.reportCallStarted(c) + defer runner.callDone(c) + dispatcher(c) + })() + return c +} + +// Same as forkCall(), but wait for call to finish before returning. +func (runner *suiteRunner) runFunc(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C { + c := runner.forkCall(method, kind, testName, logb, dispatcher) + <-c.done + return c +} + +// Handle a finished call. If there were any panics, update the call status +// accordingly. Then, mark the call as done and report to the tracker. +func (runner *suiteRunner) callDone(c *C) { + value := recover() + if value != nil { + switch v := value.(type) { + case *fixturePanic: + if v.status == skippedSt { + c.setStatus(skippedSt) + } else { + c.logSoftPanic("Fixture has panicked (see related PANIC)") + c.setStatus(fixturePanickedSt) + } + default: + c.logPanic(1, value) + c.setStatus(panickedSt) + } + } + if c.mustFail { + switch c.status() { + case failedSt: + c.setStatus(succeededSt) + case succeededSt: + c.setStatus(failedSt) + c.logString("Error: Test succeeded, but was expected to fail") + c.logString("Reason: " + c.reason) + } + } + + runner.reportCallDone(c) + c.done <- c +} + +// Runs a fixture call synchronously. The fixture will still be run in a +// goroutine like all suite methods, but this method will not return +// while the fixture goroutine is not done, because the fixture must be +// run in a desired order. +func (runner *suiteRunner) runFixture(method *methodType, testName string, logb *logger) *C { + if method != nil { + c := runner.runFunc(method, fixtureKd, testName, logb, func(c *C) { + c.ResetTimer() + c.StartTimer() + defer c.StopTimer() + c.method.Call([]reflect.Value{reflect.ValueOf(c)}) + }) + return c + } + return nil +} + +// Run the fixture method with runFixture(), but panic with a fixturePanic{} +// in case the fixture method panics. This makes it easier to track the +// fixture panic together with other call panics within forkTest(). +func (runner *suiteRunner) runFixtureWithPanic(method *methodType, testName string, logb *logger, skipped *bool) *C { + if skipped != nil && *skipped { + return nil + } + c := runner.runFixture(method, testName, logb) + if c != nil && c.status() != succeededSt { + if skipped != nil { + *skipped = c.status() == skippedSt + } + panic(&fixturePanic{c.status(), method}) + } + return c +} + +type fixturePanic struct { + status funcStatus + method *methodType +} + +// Run the suite test method, together with the test-specific fixture, +// asynchronously. +func (runner *suiteRunner) forkTest(method *methodType) *C { + testName := method.String() + return runner.forkCall(method, testKd, testName, nil, func(c *C) { + var skipped bool + defer runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, &skipped) + defer c.StopTimer() + benchN := 1 + for { + runner.runFixtureWithPanic(runner.setUpTest, testName, c.logb, &skipped) + mt := c.method.Type() + if mt.NumIn() != 1 || mt.In(0) != reflect.TypeOf(c) { + // Rather than a plain panic, provide a more helpful message when + // the argument type is incorrect. + c.setStatus(panickedSt) + c.logArgPanic(c.method, "*check.C") + return + } + if strings.HasPrefix(c.method.Info.Name, "Test") { + c.ResetTimer() + c.StartTimer() + c.method.Call([]reflect.Value{reflect.ValueOf(c)}) + return + } + if !strings.HasPrefix(c.method.Info.Name, "Benchmark") { + panic("unexpected method prefix: " + c.method.Info.Name) + } + + runtime.GC() + c.N = benchN + c.ResetTimer() + c.StartTimer() + c.method.Call([]reflect.Value{reflect.ValueOf(c)}) + c.StopTimer() + if c.status() != succeededSt || c.duration >= c.benchTime || benchN >= 1e9 { + return + } + perOpN := int(1e9) + if c.nsPerOp() != 0 { + perOpN = int(c.benchTime.Nanoseconds() / c.nsPerOp()) + } + + // Logic taken from the stock testing package: + // - Run more iterations than we think we'll need for a second (1.5x). + // - Don't grow too fast in case we had timing errors previously. + // - Be sure to run at least one more than last time. + benchN = max(min(perOpN+perOpN/2, 100*benchN), benchN+1) + benchN = roundUp(benchN) + + skipped = true // Don't run the deferred one if this panics. + runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, nil) + skipped = false + } + }) +} + +// Same as forkTest(), but wait for the test to finish before returning. +func (runner *suiteRunner) runTest(method *methodType) *C { + c := runner.forkTest(method) + <-c.done + return c +} + +// Helper to mark tests as skipped or missed. A bit heavy for what +// it does, but it enables homogeneous handling of tracking, including +// nice verbose output. +func (runner *suiteRunner) skipTests(status funcStatus, methods []*methodType) { + for _, method := range methods { + runner.runFunc(method, testKd, "", nil, func(c *C) { + c.setStatus(status) + }) + } +} + +// Verify if the fixture arguments are *check.C. In case of errors, +// log the error as a panic in the fixture method call, and return false. +func (runner *suiteRunner) checkFixtureArgs() bool { + succeeded := true + argType := reflect.TypeOf(&C{}) + for _, method := range []*methodType{runner.setUpSuite, runner.tearDownSuite, runner.setUpTest, runner.tearDownTest} { + if method != nil { + mt := method.Type() + if mt.NumIn() != 1 || mt.In(0) != argType { + succeeded = false + runner.runFunc(method, fixtureKd, "", nil, func(c *C) { + c.logArgPanic(method, "*check.C") + c.setStatus(panickedSt) + }) + } + } + } + return succeeded +} + +func (runner *suiteRunner) reportCallStarted(c *C) { + runner.output.WriteCallStarted("START", c) +} + +func (runner *suiteRunner) reportCallDone(c *C) { + runner.tracker.callDone(c) + switch c.status() { + case succeededSt: + if c.mustFail { + runner.output.WriteCallSuccess("FAIL EXPECTED", c) + } else { + runner.output.WriteCallSuccess("PASS", c) + } + case skippedSt: + runner.output.WriteCallSuccess("SKIP", c) + case failedSt: + runner.output.WriteCallProblem("FAIL", c) + case panickedSt: + runner.output.WriteCallProblem("PANIC", c) + case fixturePanickedSt: + // That's a testKd call reporting that its fixture + // has panicked. The fixture call which caused the + // panic itself was tracked above. We'll report to + // aid debugging. + runner.output.WriteCallProblem("PANIC", c) + case missedSt: + runner.output.WriteCallSuccess("MISS", c) + } +} diff --git a/vendor/gopkg.in/check.v1/checkers.go b/vendor/gopkg.in/check.v1/checkers.go new file mode 100644 index 000000000..374954587 --- /dev/null +++ b/vendor/gopkg.in/check.v1/checkers.go @@ -0,0 +1,458 @@ +package check + +import ( + "fmt" + "reflect" + "regexp" +) + +// ----------------------------------------------------------------------- +// CommentInterface and Commentf helper, to attach extra information to checks. + +type comment struct { + format string + args []interface{} +} + +// Commentf returns an infomational value to use with Assert or Check calls. +// If the checker test fails, the provided arguments will be passed to +// fmt.Sprintf, and will be presented next to the logged failure. +// +// For example: +// +// c.Assert(v, Equals, 42, Commentf("Iteration #%d failed.", i)) +// +// Note that if the comment is constant, a better option is to +// simply use a normal comment right above or next to the line, as +// it will also get printed with any errors: +// +// c.Assert(l, Equals, 8192) // Ensure buffer size is correct (bug #123) +// +func Commentf(format string, args ...interface{}) CommentInterface { + return &comment{format, args} +} + +// CommentInterface must be implemented by types that attach extra +// information to failed checks. See the Commentf function for details. +type CommentInterface interface { + CheckCommentString() string +} + +func (c *comment) CheckCommentString() string { + return fmt.Sprintf(c.format, c.args...) +} + +// ----------------------------------------------------------------------- +// The Checker interface. + +// The Checker interface must be provided by checkers used with +// the Assert and Check verification methods. +type Checker interface { + Info() *CheckerInfo + Check(params []interface{}, names []string) (result bool, error string) +} + +// See the Checker interface. +type CheckerInfo struct { + Name string + Params []string +} + +func (info *CheckerInfo) Info() *CheckerInfo { + return info +} + +// ----------------------------------------------------------------------- +// Not checker logic inverter. + +// The Not checker inverts the logic of the provided checker. The +// resulting checker will succeed where the original one failed, and +// vice-versa. +// +// For example: +// +// c.Assert(a, Not(Equals), b) +// +func Not(checker Checker) Checker { + return ¬Checker{checker} +} + +type notChecker struct { + sub Checker +} + +func (checker *notChecker) Info() *CheckerInfo { + info := *checker.sub.Info() + info.Name = "Not(" + info.Name + ")" + return &info +} + +func (checker *notChecker) Check(params []interface{}, names []string) (result bool, error string) { + result, error = checker.sub.Check(params, names) + result = !result + return +} + +// ----------------------------------------------------------------------- +// IsNil checker. + +type isNilChecker struct { + *CheckerInfo +} + +// The IsNil checker tests whether the obtained value is nil. +// +// For example: +// +// c.Assert(err, IsNil) +// +var IsNil Checker = &isNilChecker{ + &CheckerInfo{Name: "IsNil", Params: []string{"value"}}, +} + +func (checker *isNilChecker) Check(params []interface{}, names []string) (result bool, error string) { + return isNil(params[0]), "" +} + +func isNil(obtained interface{}) (result bool) { + if obtained == nil { + result = true + } else { + switch v := reflect.ValueOf(obtained); v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return v.IsNil() + } + } + return +} + +// ----------------------------------------------------------------------- +// NotNil checker. Alias for Not(IsNil), since it's so common. + +type notNilChecker struct { + *CheckerInfo +} + +// The NotNil checker verifies that the obtained value is not nil. +// +// For example: +// +// c.Assert(iface, NotNil) +// +// This is an alias for Not(IsNil), made available since it's a +// fairly common check. +// +var NotNil Checker = ¬NilChecker{ + &CheckerInfo{Name: "NotNil", Params: []string{"value"}}, +} + +func (checker *notNilChecker) Check(params []interface{}, names []string) (result bool, error string) { + return !isNil(params[0]), "" +} + +// ----------------------------------------------------------------------- +// Equals checker. + +type equalsChecker struct { + *CheckerInfo +} + +// The Equals checker verifies that the obtained value is equal to +// the expected value, according to usual Go semantics for ==. +// +// For example: +// +// c.Assert(value, Equals, 42) +// +var Equals Checker = &equalsChecker{ + &CheckerInfo{Name: "Equals", Params: []string{"obtained", "expected"}}, +} + +func (checker *equalsChecker) Check(params []interface{}, names []string) (result bool, error string) { + defer func() { + if v := recover(); v != nil { + result = false + error = fmt.Sprint(v) + } + }() + return params[0] == params[1], "" +} + +// ----------------------------------------------------------------------- +// DeepEquals checker. + +type deepEqualsChecker struct { + *CheckerInfo +} + +// The DeepEquals checker verifies that the obtained value is deep-equal to +// the expected value. The check will work correctly even when facing +// slices, interfaces, and values of different types (which always fail +// the test). +// +// For example: +// +// c.Assert(value, DeepEquals, 42) +// c.Assert(array, DeepEquals, []string{"hi", "there"}) +// +var DeepEquals Checker = &deepEqualsChecker{ + &CheckerInfo{Name: "DeepEquals", Params: []string{"obtained", "expected"}}, +} + +func (checker *deepEqualsChecker) Check(params []interface{}, names []string) (result bool, error string) { + return reflect.DeepEqual(params[0], params[1]), "" +} + +// ----------------------------------------------------------------------- +// HasLen checker. + +type hasLenChecker struct { + *CheckerInfo +} + +// The HasLen checker verifies that the obtained value has the +// provided length. In many cases this is superior to using Equals +// in conjunction with the len function because in case the check +// fails the value itself will be printed, instead of its length, +// providing more details for figuring the problem. +// +// For example: +// +// c.Assert(list, HasLen, 5) +// +var HasLen Checker = &hasLenChecker{ + &CheckerInfo{Name: "HasLen", Params: []string{"obtained", "n"}}, +} + +func (checker *hasLenChecker) Check(params []interface{}, names []string) (result bool, error string) { + n, ok := params[1].(int) + if !ok { + return false, "n must be an int" + } + value := reflect.ValueOf(params[0]) + switch value.Kind() { + case reflect.Map, reflect.Array, reflect.Slice, reflect.Chan, reflect.String: + default: + return false, "obtained value type has no length" + } + return value.Len() == n, "" +} + +// ----------------------------------------------------------------------- +// ErrorMatches checker. + +type errorMatchesChecker struct { + *CheckerInfo +} + +// The ErrorMatches checker verifies that the error value +// is non nil and matches the regular expression provided. +// +// For example: +// +// c.Assert(err, ErrorMatches, "perm.*denied") +// +var ErrorMatches Checker = errorMatchesChecker{ + &CheckerInfo{Name: "ErrorMatches", Params: []string{"value", "regex"}}, +} + +func (checker errorMatchesChecker) Check(params []interface{}, names []string) (result bool, errStr string) { + if params[0] == nil { + return false, "Error value is nil" + } + err, ok := params[0].(error) + if !ok { + return false, "Value is not an error" + } + params[0] = err.Error() + names[0] = "error" + return matches(params[0], params[1]) +} + +// ----------------------------------------------------------------------- +// Matches checker. + +type matchesChecker struct { + *CheckerInfo +} + +// The Matches checker verifies that the string provided as the obtained +// value (or the string resulting from obtained.String()) matches the +// regular expression provided. +// +// For example: +// +// c.Assert(err, Matches, "perm.*denied") +// +var Matches Checker = &matchesChecker{ + &CheckerInfo{Name: "Matches", Params: []string{"value", "regex"}}, +} + +func (checker *matchesChecker) Check(params []interface{}, names []string) (result bool, error string) { + return matches(params[0], params[1]) +} + +func matches(value, regex interface{}) (result bool, error string) { + reStr, ok := regex.(string) + if !ok { + return false, "Regex must be a string" + } + valueStr, valueIsStr := value.(string) + if !valueIsStr { + if valueWithStr, valueHasStr := value.(fmt.Stringer); valueHasStr { + valueStr, valueIsStr = valueWithStr.String(), true + } + } + if valueIsStr { + matches, err := regexp.MatchString("^"+reStr+"$", valueStr) + if err != nil { + return false, "Can't compile regex: " + err.Error() + } + return matches, "" + } + return false, "Obtained value is not a string and has no .String()" +} + +// ----------------------------------------------------------------------- +// Panics checker. + +type panicsChecker struct { + *CheckerInfo +} + +// The Panics checker verifies that calling the provided zero-argument +// function will cause a panic which is deep-equal to the provided value. +// +// For example: +// +// c.Assert(func() { f(1, 2) }, Panics, &SomeErrorType{"BOOM"}). +// +// +var Panics Checker = &panicsChecker{ + &CheckerInfo{Name: "Panics", Params: []string{"function", "expected"}}, +} + +func (checker *panicsChecker) Check(params []interface{}, names []string) (result bool, error string) { + f := reflect.ValueOf(params[0]) + if f.Kind() != reflect.Func || f.Type().NumIn() != 0 { + return false, "Function must take zero arguments" + } + defer func() { + // If the function has not panicked, then don't do the check. + if error != "" { + return + } + params[0] = recover() + names[0] = "panic" + result = reflect.DeepEqual(params[0], params[1]) + }() + f.Call(nil) + return false, "Function has not panicked" +} + +type panicMatchesChecker struct { + *CheckerInfo +} + +// The PanicMatches checker verifies that calling the provided zero-argument +// function will cause a panic with an error value matching +// the regular expression provided. +// +// For example: +// +// c.Assert(func() { f(1, 2) }, PanicMatches, `open.*: no such file or directory`). +// +// +var PanicMatches Checker = &panicMatchesChecker{ + &CheckerInfo{Name: "PanicMatches", Params: []string{"function", "expected"}}, +} + +func (checker *panicMatchesChecker) Check(params []interface{}, names []string) (result bool, errmsg string) { + f := reflect.ValueOf(params[0]) + if f.Kind() != reflect.Func || f.Type().NumIn() != 0 { + return false, "Function must take zero arguments" + } + defer func() { + // If the function has not panicked, then don't do the check. + if errmsg != "" { + return + } + obtained := recover() + names[0] = "panic" + if e, ok := obtained.(error); ok { + params[0] = e.Error() + } else if _, ok := obtained.(string); ok { + params[0] = obtained + } else { + errmsg = "Panic value is not a string or an error" + return + } + result, errmsg = matches(params[0], params[1]) + }() + f.Call(nil) + return false, "Function has not panicked" +} + +// ----------------------------------------------------------------------- +// FitsTypeOf checker. + +type fitsTypeChecker struct { + *CheckerInfo +} + +// The FitsTypeOf checker verifies that the obtained value is +// assignable to a variable with the same type as the provided +// sample value. +// +// For example: +// +// c.Assert(value, FitsTypeOf, int64(0)) +// c.Assert(value, FitsTypeOf, os.Error(nil)) +// +var FitsTypeOf Checker = &fitsTypeChecker{ + &CheckerInfo{Name: "FitsTypeOf", Params: []string{"obtained", "sample"}}, +} + +func (checker *fitsTypeChecker) Check(params []interface{}, names []string) (result bool, error string) { + obtained := reflect.ValueOf(params[0]) + sample := reflect.ValueOf(params[1]) + if !obtained.IsValid() { + return false, "" + } + if !sample.IsValid() { + return false, "Invalid sample value" + } + return obtained.Type().AssignableTo(sample.Type()), "" +} + +// ----------------------------------------------------------------------- +// Implements checker. + +type implementsChecker struct { + *CheckerInfo +} + +// The Implements checker verifies that the obtained value +// implements the interface specified via a pointer to an interface +// variable. +// +// For example: +// +// var e os.Error +// c.Assert(err, Implements, &e) +// +var Implements Checker = &implementsChecker{ + &CheckerInfo{Name: "Implements", Params: []string{"obtained", "ifaceptr"}}, +} + +func (checker *implementsChecker) Check(params []interface{}, names []string) (result bool, error string) { + obtained := reflect.ValueOf(params[0]) + ifaceptr := reflect.ValueOf(params[1]) + if !obtained.IsValid() { + return false, "" + } + if !ifaceptr.IsValid() || ifaceptr.Kind() != reflect.Ptr || ifaceptr.Elem().Kind() != reflect.Interface { + return false, "ifaceptr should be a pointer to an interface variable" + } + return obtained.Type().Implements(ifaceptr.Elem().Type()), "" +} diff --git a/vendor/gopkg.in/check.v1/helpers.go b/vendor/gopkg.in/check.v1/helpers.go new file mode 100644 index 000000000..58a733b50 --- /dev/null +++ b/vendor/gopkg.in/check.v1/helpers.go @@ -0,0 +1,231 @@ +package check + +import ( + "fmt" + "strings" + "time" +) + +// TestName returns the current test name in the form "SuiteName.TestName" +func (c *C) TestName() string { + return c.testName +} + +// ----------------------------------------------------------------------- +// Basic succeeding/failing logic. + +// Failed returns whether the currently running test has already failed. +func (c *C) Failed() bool { + return c.status() == failedSt +} + +// Fail marks the currently running test as failed. +// +// Something ought to have been previously logged so the developer can tell +// what went wrong. The higher level helper functions will fail the test +// and do the logging properly. +func (c *C) Fail() { + c.setStatus(failedSt) +} + +// FailNow marks the currently running test as failed and stops running it. +// Something ought to have been previously logged so the developer can tell +// what went wrong. The higher level helper functions will fail the test +// and do the logging properly. +func (c *C) FailNow() { + c.Fail() + c.stopNow() +} + +// Succeed marks the currently running test as succeeded, undoing any +// previous failures. +func (c *C) Succeed() { + c.setStatus(succeededSt) +} + +// SucceedNow marks the currently running test as succeeded, undoing any +// previous failures, and stops running the test. +func (c *C) SucceedNow() { + c.Succeed() + c.stopNow() +} + +// ExpectFailure informs that the running test is knowingly broken for +// the provided reason. If the test does not fail, an error will be reported +// to raise attention to this fact. This method is useful to temporarily +// disable tests which cover well known problems until a better time to +// fix the problem is found, without forgetting about the fact that a +// failure still exists. +func (c *C) ExpectFailure(reason string) { + if reason == "" { + panic("Missing reason why the test is expected to fail") + } + c.mustFail = true + c.reason = reason +} + +// Skip skips the running test for the provided reason. If run from within +// SetUpTest, the individual test being set up will be skipped, and if run +// from within SetUpSuite, the whole suite is skipped. +func (c *C) Skip(reason string) { + if reason == "" { + panic("Missing reason why the test is being skipped") + } + c.reason = reason + c.setStatus(skippedSt) + c.stopNow() +} + +// ----------------------------------------------------------------------- +// Basic logging. + +// GetTestLog returns the current test error output. +func (c *C) GetTestLog() string { + return c.logb.String() +} + +// Log logs some information into the test error output. +// The provided arguments are assembled together into a string with fmt.Sprint. +func (c *C) Log(args ...interface{}) { + c.log(args...) +} + +// Log logs some information into the test error output. +// The provided arguments are assembled together into a string with fmt.Sprintf. +func (c *C) Logf(format string, args ...interface{}) { + c.logf(format, args...) +} + +// Output enables *C to be used as a logger in functions that require only +// the minimum interface of *log.Logger. +func (c *C) Output(calldepth int, s string) error { + d := time.Now().Sub(c.startTime) + msec := d / time.Millisecond + sec := d / time.Second + min := d / time.Minute + + c.Logf("[LOG] %d:%02d.%03d %s", min, sec%60, msec%1000, s) + return nil +} + +// Error logs an error into the test error output and marks the test as failed. +// The provided arguments are assembled together into a string with fmt.Sprint. +func (c *C) Error(args ...interface{}) { + c.logCaller(1) + c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...))) + c.logNewLine() + c.Fail() +} + +// Errorf logs an error into the test error output and marks the test as failed. +// The provided arguments are assembled together into a string with fmt.Sprintf. +func (c *C) Errorf(format string, args ...interface{}) { + c.logCaller(1) + c.logString(fmt.Sprintf("Error: "+format, args...)) + c.logNewLine() + c.Fail() +} + +// Fatal logs an error into the test error output, marks the test as failed, and +// stops the test execution. The provided arguments are assembled together into +// a string with fmt.Sprint. +func (c *C) Fatal(args ...interface{}) { + c.logCaller(1) + c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...))) + c.logNewLine() + c.FailNow() +} + +// Fatlaf logs an error into the test error output, marks the test as failed, and +// stops the test execution. The provided arguments are assembled together into +// a string with fmt.Sprintf. +func (c *C) Fatalf(format string, args ...interface{}) { + c.logCaller(1) + c.logString(fmt.Sprint("Error: ", fmt.Sprintf(format, args...))) + c.logNewLine() + c.FailNow() +} + +// ----------------------------------------------------------------------- +// Generic checks and assertions based on checkers. + +// Check verifies if the first value matches the expected value according +// to the provided checker. If they do not match, an error is logged, the +// test is marked as failed, and the test execution continues. +// +// Some checkers may not need the expected argument (e.g. IsNil). +// +// Extra arguments provided to the function are logged next to the reported +// problem when the matching fails. +func (c *C) Check(obtained interface{}, checker Checker, args ...interface{}) bool { + return c.internalCheck("Check", obtained, checker, args...) +} + +// Assert ensures that the first value matches the expected value according +// to the provided checker. If they do not match, an error is logged, the +// test is marked as failed, and the test execution stops. +// +// Some checkers may not need the expected argument (e.g. IsNil). +// +// Extra arguments provided to the function are logged next to the reported +// problem when the matching fails. +func (c *C) Assert(obtained interface{}, checker Checker, args ...interface{}) { + if !c.internalCheck("Assert", obtained, checker, args...) { + c.stopNow() + } +} + +func (c *C) internalCheck(funcName string, obtained interface{}, checker Checker, args ...interface{}) bool { + if checker == nil { + c.logCaller(2) + c.logString(fmt.Sprintf("%s(obtained, nil!?, ...):", funcName)) + c.logString("Oops.. you've provided a nil checker!") + c.logNewLine() + c.Fail() + return false + } + + // If the last argument is a bug info, extract it out. + var comment CommentInterface + if len(args) > 0 { + if c, ok := args[len(args)-1].(CommentInterface); ok { + comment = c + args = args[:len(args)-1] + } + } + + params := append([]interface{}{obtained}, args...) + info := checker.Info() + + if len(params) != len(info.Params) { + names := append([]string{info.Params[0], info.Name}, info.Params[1:]...) + c.logCaller(2) + c.logString(fmt.Sprintf("%s(%s):", funcName, strings.Join(names, ", "))) + c.logString(fmt.Sprintf("Wrong number of parameters for %s: want %d, got %d", info.Name, len(names), len(params)+1)) + c.logNewLine() + c.Fail() + return false + } + + // Copy since it may be mutated by Check. + names := append([]string{}, info.Params...) + + // Do the actual check. + result, error := checker.Check(params, names) + if !result || error != "" { + c.logCaller(2) + for i := 0; i != len(params); i++ { + c.logValue(names[i], params[i]) + } + if comment != nil { + c.logString(comment.CheckCommentString()) + } + if error != "" { + c.logString(error) + } + c.logNewLine() + c.Fail() + return false + } + return true +} diff --git a/vendor/gopkg.in/check.v1/printer.go b/vendor/gopkg.in/check.v1/printer.go new file mode 100644 index 000000000..e0f7557b5 --- /dev/null +++ b/vendor/gopkg.in/check.v1/printer.go @@ -0,0 +1,168 @@ +package check + +import ( + "bytes" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "os" +) + +func indent(s, with string) (r string) { + eol := true + for i := 0; i != len(s); i++ { + c := s[i] + switch { + case eol && c == '\n' || c == '\r': + case c == '\n' || c == '\r': + eol = true + case eol: + eol = false + s = s[:i] + with + s[i:] + i += len(with) + } + } + return s +} + +func printLine(filename string, line int) (string, error) { + fset := token.NewFileSet() + file, err := os.Open(filename) + if err != nil { + return "", err + } + fnode, err := parser.ParseFile(fset, filename, file, parser.ParseComments) + if err != nil { + return "", err + } + config := &printer.Config{Mode: printer.UseSpaces, Tabwidth: 4} + lp := &linePrinter{fset: fset, fnode: fnode, line: line, config: config} + ast.Walk(lp, fnode) + result := lp.output.Bytes() + // Comments leave \n at the end. + n := len(result) + for n > 0 && result[n-1] == '\n' { + n-- + } + return string(result[:n]), nil +} + +type linePrinter struct { + config *printer.Config + fset *token.FileSet + fnode *ast.File + line int + output bytes.Buffer + stmt ast.Stmt +} + +func (lp *linePrinter) emit() bool { + if lp.stmt != nil { + lp.trim(lp.stmt) + lp.printWithComments(lp.stmt) + lp.stmt = nil + return true + } + return false +} + +func (lp *linePrinter) printWithComments(n ast.Node) { + nfirst := lp.fset.Position(n.Pos()).Line + nlast := lp.fset.Position(n.End()).Line + for _, g := range lp.fnode.Comments { + cfirst := lp.fset.Position(g.Pos()).Line + clast := lp.fset.Position(g.End()).Line + if clast == nfirst-1 && lp.fset.Position(n.Pos()).Column == lp.fset.Position(g.Pos()).Column { + for _, c := range g.List { + lp.output.WriteString(c.Text) + lp.output.WriteByte('\n') + } + } + if cfirst >= nfirst && cfirst <= nlast && n.End() <= g.List[0].Slash { + // The printer will not include the comment if it starts past + // the node itself. Trick it into printing by overlapping the + // slash with the end of the statement. + g.List[0].Slash = n.End() - 1 + } + } + node := &printer.CommentedNode{n, lp.fnode.Comments} + lp.config.Fprint(&lp.output, lp.fset, node) +} + +func (lp *linePrinter) Visit(n ast.Node) (w ast.Visitor) { + if n == nil { + if lp.output.Len() == 0 { + lp.emit() + } + return nil + } + first := lp.fset.Position(n.Pos()).Line + last := lp.fset.Position(n.End()).Line + if first <= lp.line && last >= lp.line { + // Print the innermost statement containing the line. + if stmt, ok := n.(ast.Stmt); ok { + if _, ok := n.(*ast.BlockStmt); !ok { + lp.stmt = stmt + } + } + if first == lp.line && lp.emit() { + return nil + } + return lp + } + return nil +} + +func (lp *linePrinter) trim(n ast.Node) bool { + stmt, ok := n.(ast.Stmt) + if !ok { + return true + } + line := lp.fset.Position(n.Pos()).Line + if line != lp.line { + return false + } + switch stmt := stmt.(type) { + case *ast.IfStmt: + stmt.Body = lp.trimBlock(stmt.Body) + case *ast.SwitchStmt: + stmt.Body = lp.trimBlock(stmt.Body) + case *ast.TypeSwitchStmt: + stmt.Body = lp.trimBlock(stmt.Body) + case *ast.CaseClause: + stmt.Body = lp.trimList(stmt.Body) + case *ast.CommClause: + stmt.Body = lp.trimList(stmt.Body) + case *ast.BlockStmt: + stmt.List = lp.trimList(stmt.List) + } + return true +} + +func (lp *linePrinter) trimBlock(stmt *ast.BlockStmt) *ast.BlockStmt { + if !lp.trim(stmt) { + return lp.emptyBlock(stmt) + } + stmt.Rbrace = stmt.Lbrace + return stmt +} + +func (lp *linePrinter) trimList(stmts []ast.Stmt) []ast.Stmt { + for i := 0; i != len(stmts); i++ { + if !lp.trim(stmts[i]) { + stmts[i] = lp.emptyStmt(stmts[i]) + break + } + } + return stmts +} + +func (lp *linePrinter) emptyStmt(n ast.Node) *ast.ExprStmt { + return &ast.ExprStmt{&ast.Ellipsis{n.Pos(), nil}} +} + +func (lp *linePrinter) emptyBlock(n ast.Node) *ast.BlockStmt { + p := n.Pos() + return &ast.BlockStmt{p, []ast.Stmt{lp.emptyStmt(n)}, p} +} diff --git a/vendor/gopkg.in/check.v1/reporter.go b/vendor/gopkg.in/check.v1/reporter.go new file mode 100644 index 000000000..fb04f76f6 --- /dev/null +++ b/vendor/gopkg.in/check.v1/reporter.go @@ -0,0 +1,88 @@ +package check + +import ( + "fmt" + "io" + "sync" +) + +// ----------------------------------------------------------------------- +// Output writer manages atomic output writing according to settings. + +type outputWriter struct { + m sync.Mutex + writer io.Writer + wroteCallProblemLast bool + Stream bool + Verbose bool +} + +func newOutputWriter(writer io.Writer, stream, verbose bool) *outputWriter { + return &outputWriter{writer: writer, Stream: stream, Verbose: verbose} +} + +func (ow *outputWriter) Write(content []byte) (n int, err error) { + ow.m.Lock() + n, err = ow.writer.Write(content) + ow.m.Unlock() + return +} + +func (ow *outputWriter) WriteCallStarted(label string, c *C) { + if ow.Stream { + header := renderCallHeader(label, c, "", "\n") + ow.m.Lock() + ow.writer.Write([]byte(header)) + ow.m.Unlock() + } +} + +func (ow *outputWriter) WriteCallProblem(label string, c *C) { + var prefix string + if !ow.Stream { + prefix = "\n-----------------------------------" + + "-----------------------------------\n" + } + header := renderCallHeader(label, c, prefix, "\n\n") + ow.m.Lock() + ow.wroteCallProblemLast = true + ow.writer.Write([]byte(header)) + if !ow.Stream { + c.logb.WriteTo(ow.writer) + } + ow.m.Unlock() +} + +func (ow *outputWriter) WriteCallSuccess(label string, c *C) { + if ow.Stream || (ow.Verbose && c.kind == testKd) { + // TODO Use a buffer here. + var suffix string + if c.reason != "" { + suffix = " (" + c.reason + ")" + } + if c.status() == succeededSt { + suffix += "\t" + c.timerString() + } + suffix += "\n" + if ow.Stream { + suffix += "\n" + } + header := renderCallHeader(label, c, "", suffix) + ow.m.Lock() + // Resist temptation of using line as prefix above due to race. + if !ow.Stream && ow.wroteCallProblemLast { + header = "\n-----------------------------------" + + "-----------------------------------\n" + + header + } + ow.wroteCallProblemLast = false + ow.writer.Write([]byte(header)) + ow.m.Unlock() + } +} + +func renderCallHeader(label string, c *C, prefix, suffix string) string { + pc := c.method.PC() + return fmt.Sprintf("%s%s: %s: %s%s", prefix, label, niceFuncPath(pc), + niceFuncName(pc), suffix) +} diff --git a/vendor/gopkg.in/check.v1/run.go b/vendor/gopkg.in/check.v1/run.go new file mode 100644 index 000000000..da8fd7987 --- /dev/null +++ b/vendor/gopkg.in/check.v1/run.go @@ -0,0 +1,175 @@ +package check + +import ( + "bufio" + "flag" + "fmt" + "os" + "testing" + "time" +) + +// ----------------------------------------------------------------------- +// Test suite registry. + +var allSuites []interface{} + +// Suite registers the given value as a test suite to be run. Any methods +// starting with the Test prefix in the given value will be considered as +// a test method. +func Suite(suite interface{}) interface{} { + allSuites = append(allSuites, suite) + return suite +} + +// ----------------------------------------------------------------------- +// Public running interface. + +var ( + oldFilterFlag = flag.String("gocheck.f", "", "Regular expression selecting which tests and/or suites to run") + oldVerboseFlag = flag.Bool("gocheck.v", false, "Verbose mode") + oldStreamFlag = flag.Bool("gocheck.vv", false, "Super verbose mode (disables output caching)") + oldBenchFlag = flag.Bool("gocheck.b", false, "Run benchmarks") + oldBenchTime = flag.Duration("gocheck.btime", 1*time.Second, "approximate run time for each benchmark") + oldListFlag = flag.Bool("gocheck.list", false, "List the names of all tests that will be run") + oldWorkFlag = flag.Bool("gocheck.work", false, "Display and do not remove the test working directory") + + newFilterFlag = flag.String("check.f", "", "Regular expression selecting which tests and/or suites to run") + newVerboseFlag = flag.Bool("check.v", false, "Verbose mode") + newStreamFlag = flag.Bool("check.vv", false, "Super verbose mode (disables output caching)") + newBenchFlag = flag.Bool("check.b", false, "Run benchmarks") + newBenchTime = flag.Duration("check.btime", 1*time.Second, "approximate run time for each benchmark") + newBenchMem = flag.Bool("check.bmem", false, "Report memory benchmarks") + newListFlag = flag.Bool("check.list", false, "List the names of all tests that will be run") + newWorkFlag = flag.Bool("check.work", false, "Display and do not remove the test working directory") +) + +// TestingT runs all test suites registered with the Suite function, +// printing results to stdout, and reporting any failures back to +// the "testing" package. +func TestingT(testingT *testing.T) { + benchTime := *newBenchTime + if benchTime == 1*time.Second { + benchTime = *oldBenchTime + } + conf := &RunConf{ + Filter: *oldFilterFlag + *newFilterFlag, + Verbose: *oldVerboseFlag || *newVerboseFlag, + Stream: *oldStreamFlag || *newStreamFlag, + Benchmark: *oldBenchFlag || *newBenchFlag, + BenchmarkTime: benchTime, + BenchmarkMem: *newBenchMem, + KeepWorkDir: *oldWorkFlag || *newWorkFlag, + } + if *oldListFlag || *newListFlag { + w := bufio.NewWriter(os.Stdout) + for _, name := range ListAll(conf) { + fmt.Fprintln(w, name) + } + w.Flush() + return + } + result := RunAll(conf) + println(result.String()) + if !result.Passed() { + testingT.Fail() + } +} + +// RunAll runs all test suites registered with the Suite function, using the +// provided run configuration. +func RunAll(runConf *RunConf) *Result { + result := Result{} + for _, suite := range allSuites { + result.Add(Run(suite, runConf)) + } + return &result +} + +// Run runs the provided test suite using the provided run configuration. +func Run(suite interface{}, runConf *RunConf) *Result { + runner := newSuiteRunner(suite, runConf) + return runner.run() +} + +// ListAll returns the names of all the test functions registered with the +// Suite function that will be run with the provided run configuration. +func ListAll(runConf *RunConf) []string { + var names []string + for _, suite := range allSuites { + names = append(names, List(suite, runConf)...) + } + return names +} + +// List returns the names of the test functions in the given +// suite that will be run with the provided run configuration. +func List(suite interface{}, runConf *RunConf) []string { + var names []string + runner := newSuiteRunner(suite, runConf) + for _, t := range runner.tests { + names = append(names, t.String()) + } + return names +} + +// ----------------------------------------------------------------------- +// Result methods. + +func (r *Result) Add(other *Result) { + r.Succeeded += other.Succeeded + r.Skipped += other.Skipped + r.Failed += other.Failed + r.Panicked += other.Panicked + r.FixturePanicked += other.FixturePanicked + r.ExpectedFailures += other.ExpectedFailures + r.Missed += other.Missed + if r.WorkDir != "" && other.WorkDir != "" { + r.WorkDir += ":" + other.WorkDir + } else if other.WorkDir != "" { + r.WorkDir = other.WorkDir + } +} + +func (r *Result) Passed() bool { + return (r.Failed == 0 && r.Panicked == 0 && + r.FixturePanicked == 0 && r.Missed == 0 && + r.RunError == nil) +} + +func (r *Result) String() string { + if r.RunError != nil { + return "ERROR: " + r.RunError.Error() + } + + var value string + if r.Failed == 0 && r.Panicked == 0 && r.FixturePanicked == 0 && + r.Missed == 0 { + value = "OK: " + } else { + value = "OOPS: " + } + value += fmt.Sprintf("%d passed", r.Succeeded) + if r.Skipped != 0 { + value += fmt.Sprintf(", %d skipped", r.Skipped) + } + if r.ExpectedFailures != 0 { + value += fmt.Sprintf(", %d expected failures", r.ExpectedFailures) + } + if r.Failed != 0 { + value += fmt.Sprintf(", %d FAILED", r.Failed) + } + if r.Panicked != 0 { + value += fmt.Sprintf(", %d PANICKED", r.Panicked) + } + if r.FixturePanicked != 0 { + value += fmt.Sprintf(", %d FIXTURE-PANICKED", r.FixturePanicked) + } + if r.Missed != 0 { + value += fmt.Sprintf(", %d MISSED", r.Missed) + } + if r.WorkDir != "" { + value += "\nWORK=" + r.WorkDir + } + return value +} diff --git a/vendor/k8s.io/client-go/dynamic/BUILD b/vendor/k8s.io/client-go/dynamic/BUILD new file mode 100644 index 000000000..e0320e266 --- /dev/null +++ b/vendor/k8s.io/client-go/dynamic/BUILD @@ -0,0 +1,55 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = [ + "client_test.go", + "dynamic_util_test.go", + ], + library = ":go_default_library", + tags = ["automanaged"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/rest/watch:go_default_library", + ], +) + +go_library( + name = "go_default_library", + srcs = [ + "client.go", + "client_pool.go", + "dynamic_util.go", + ], + tags = ["automanaged"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion/queryparams:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", + "//vendor/k8s.io/client-go/pkg/api/v1:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", + ], +) diff --git a/vendor/k8s.io/client-go/dynamic/client.go b/vendor/k8s.io/client-go/dynamic/client.go new file mode 100644 index 000000000..a0c182094 --- /dev/null +++ b/vendor/k8s.io/client-go/dynamic/client.go @@ -0,0 +1,306 @@ +/* +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 dynamic provides a client interface to arbitrary Kubernetes +// APIs that exposes common high level operations and exposes common +// metadata. +package dynamic + +import ( + "encoding/json" + "errors" + "io" + "net/url" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/conversion/queryparams" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/pkg/api/v1" + restclient "k8s.io/client-go/rest" + "k8s.io/client-go/util/flowcontrol" +) + +// Client is a Kubernetes client that allows you to access metadata +// and manipulate metadata of a Kubernetes API group. +type Client struct { + cl *restclient.RESTClient + parameterCodec runtime.ParameterCodec +} + +// NewClient returns a new client based on the passed in config. The +// codec is ignored, as the dynamic client uses it's own codec. +func NewClient(conf *restclient.Config) (*Client, error) { + // avoid changing the original config + confCopy := *conf + conf = &confCopy + + contentConfig := ContentConfig() + contentConfig.GroupVersion = conf.GroupVersion + if conf.NegotiatedSerializer != nil { + contentConfig.NegotiatedSerializer = conf.NegotiatedSerializer + } + conf.ContentConfig = contentConfig + + if conf.APIPath == "" { + conf.APIPath = "/api" + } + + if len(conf.UserAgent) == 0 { + conf.UserAgent = restclient.DefaultKubernetesUserAgent() + } + + cl, err := restclient.RESTClientFor(conf) + if err != nil { + return nil, err + } + + return &Client{cl: cl}, nil +} + +// GetRateLimiter returns rate limier. +func (c *Client) GetRateLimiter() flowcontrol.RateLimiter { + return c.cl.GetRateLimiter() +} + +// Resource returns an API interface to the specified resource for this client's +// group and version. If resource is not a namespaced resource, then namespace +// is ignored. The ResourceClient inherits the parameter codec of c. +func (c *Client) Resource(resource *metav1.APIResource, namespace string) *ResourceClient { + return &ResourceClient{ + cl: c.cl, + resource: resource, + ns: namespace, + parameterCodec: c.parameterCodec, + } +} + +// ParameterCodec returns a client with the provided parameter codec. +func (c *Client) ParameterCodec(parameterCodec runtime.ParameterCodec) *Client { + return &Client{ + cl: c.cl, + parameterCodec: parameterCodec, + } +} + +// ResourceClient is an API interface to a specific resource under a +// dynamic client. +type ResourceClient struct { + cl *restclient.RESTClient + resource *metav1.APIResource + ns string + parameterCodec runtime.ParameterCodec +} + +// List returns a list of objects for this resource. +func (rc *ResourceClient) List(opts metav1.ListOptions) (runtime.Object, error) { + parameterEncoder := rc.parameterCodec + if parameterEncoder == nil { + parameterEncoder = defaultParameterEncoder + } + return rc.cl.Get(). + NamespaceIfScoped(rc.ns, rc.resource.Namespaced). + Resource(rc.resource.Name). + VersionedParams(&opts, parameterEncoder). + Do(). + Get() +} + +// Get gets the resource with the specified name. +func (rc *ResourceClient) Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) { + parameterEncoder := rc.parameterCodec + if parameterEncoder == nil { + parameterEncoder = defaultParameterEncoder + } + result := new(unstructured.Unstructured) + err := rc.cl.Get(). + NamespaceIfScoped(rc.ns, rc.resource.Namespaced). + Resource(rc.resource.Name). + VersionedParams(&opts, parameterEncoder). + Name(name). + Do(). + Into(result) + return result, err +} + +// Delete deletes the resource with the specified name. +func (rc *ResourceClient) Delete(name string, opts *metav1.DeleteOptions) error { + return rc.cl.Delete(). + NamespaceIfScoped(rc.ns, rc.resource.Namespaced). + Resource(rc.resource.Name). + Name(name). + Body(opts). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (rc *ResourceClient) DeleteCollection(deleteOptions *metav1.DeleteOptions, listOptions metav1.ListOptions) error { + parameterEncoder := rc.parameterCodec + if parameterEncoder == nil { + parameterEncoder = defaultParameterEncoder + } + return rc.cl.Delete(). + NamespaceIfScoped(rc.ns, rc.resource.Namespaced). + Resource(rc.resource.Name). + VersionedParams(&listOptions, parameterEncoder). + Body(deleteOptions). + Do(). + Error() +} + +// Create creates the provided resource. +func (rc *ResourceClient) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + result := new(unstructured.Unstructured) + err := rc.cl.Post(). + NamespaceIfScoped(rc.ns, rc.resource.Namespaced). + Resource(rc.resource.Name). + Body(obj). + Do(). + Into(result) + return result, err +} + +// Update updates the provided resource. +func (rc *ResourceClient) Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + result := new(unstructured.Unstructured) + if len(obj.GetName()) == 0 { + return result, errors.New("object missing name") + } + err := rc.cl.Put(). + NamespaceIfScoped(rc.ns, rc.resource.Namespaced). + Resource(rc.resource.Name). + Name(obj.GetName()). + Body(obj). + Do(). + Into(result) + return result, err +} + +// Watch returns a watch.Interface that watches the resource. +func (rc *ResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) { + parameterEncoder := rc.parameterCodec + if parameterEncoder == nil { + parameterEncoder = defaultParameterEncoder + } + opts.Watch = true + return rc.cl.Get(). + NamespaceIfScoped(rc.ns, rc.resource.Namespaced). + Resource(rc.resource.Name). + VersionedParams(&opts, parameterEncoder). + Watch() +} + +func (rc *ResourceClient) Patch(name string, pt types.PatchType, data []byte) (*unstructured.Unstructured, error) { + result := new(unstructured.Unstructured) + err := rc.cl.Patch(pt). + NamespaceIfScoped(rc.ns, rc.resource.Namespaced). + Resource(rc.resource.Name). + Name(name). + Body(data). + Do(). + Into(result) + return result, err +} + +// dynamicCodec is a codec that wraps the standard unstructured codec +// with special handling for Status objects. +type dynamicCodec struct{} + +func (dynamicCodec) Decode(data []byte, gvk *schema.GroupVersionKind, obj runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + obj, gvk, err := unstructured.UnstructuredJSONScheme.Decode(data, gvk, obj) + if err != nil { + return nil, nil, err + } + + if _, ok := obj.(*metav1.Status); !ok && strings.ToLower(gvk.Kind) == "status" { + obj = &metav1.Status{} + err := json.Unmarshal(data, obj) + if err != nil { + return nil, nil, err + } + } + + return obj, gvk, nil +} + +func (dynamicCodec) Encode(obj runtime.Object, w io.Writer) error { + return unstructured.UnstructuredJSONScheme.Encode(obj, w) +} + +// ContentConfig returns a restclient.ContentConfig for dynamic types. +func ContentConfig() restclient.ContentConfig { + var jsonInfo runtime.SerializerInfo + // TODO: scheme.Codecs here should become "pkg/apis/server/scheme" which is the minimal core you need + // to talk to a kubernetes server + for _, info := range scheme.Codecs.SupportedMediaTypes() { + if info.MediaType == runtime.ContentTypeJSON { + jsonInfo = info + break + } + } + + jsonInfo.Serializer = dynamicCodec{} + jsonInfo.PrettySerializer = nil + return restclient.ContentConfig{ + AcceptContentTypes: runtime.ContentTypeJSON, + ContentType: runtime.ContentTypeJSON, + NegotiatedSerializer: serializer.NegotiatedSerializerWrapper(jsonInfo), + } +} + +// paramaterCodec is a codec converts an API object to query +// parameters without trying to convert to the target version. +type parameterCodec struct{} + +func (parameterCodec) EncodeParameters(obj runtime.Object, to schema.GroupVersion) (url.Values, error) { + return queryparams.Convert(obj) +} + +func (parameterCodec) DecodeParameters(parameters url.Values, from schema.GroupVersion, into runtime.Object) error { + return errors.New("DecodeParameters not implemented on dynamic parameterCodec") +} + +var defaultParameterEncoder runtime.ParameterCodec = parameterCodec{} + +type versionedParameterEncoderWithV1Fallback struct{} + +func (versionedParameterEncoderWithV1Fallback) EncodeParameters(obj runtime.Object, to schema.GroupVersion) (url.Values, error) { + ret, err := scheme.ParameterCodec.EncodeParameters(obj, to) + if err != nil && runtime.IsNotRegisteredError(err) { + // fallback to v1 + return scheme.ParameterCodec.EncodeParameters(obj, v1.SchemeGroupVersion) + } + return ret, err +} + +func (versionedParameterEncoderWithV1Fallback) DecodeParameters(parameters url.Values, from schema.GroupVersion, into runtime.Object) error { + return errors.New("DecodeParameters not implemented on versionedParameterEncoderWithV1Fallback") +} + +// VersionedParameterEncoderWithV1Fallback is useful for encoding query +// parameters for thirdparty resources. It tries to convert object to the +// specified version before converting it to query parameters, and falls back to +// converting to v1 if the object is not registered in the specified version. +// For the record, currently API server always treats query parameters sent to a +// thirdparty resource endpoint as v1. +var VersionedParameterEncoderWithV1Fallback runtime.ParameterCodec = versionedParameterEncoderWithV1Fallback{} diff --git a/vendor/k8s.io/client-go/dynamic/client_pool.go b/vendor/k8s.io/client-go/dynamic/client_pool.go new file mode 100644 index 000000000..f4f2596a4 --- /dev/null +++ b/vendor/k8s.io/client-go/dynamic/client_pool.go @@ -0,0 +1,122 @@ +/* +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 dynamic + +import ( + "sync" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" + restclient "k8s.io/client-go/rest" +) + +// ClientPool manages a pool of dynamic clients. +type ClientPool interface { + // ClientForGroupVersionKind returns a client configured for the specified groupVersionResource. + // Resource may be empty. + ClientForGroupVersionResource(resource schema.GroupVersionResource) (*Client, error) + // ClientForGroupVersionKind returns a client configured for the specified groupVersionKind. + // Kind may be empty. + ClientForGroupVersionKind(kind schema.GroupVersionKind) (*Client, error) +} + +// APIPathResolverFunc knows how to convert a groupVersion to its API path. The Kind field is +// optional. +type APIPathResolverFunc func(kind schema.GroupVersionKind) string + +// LegacyAPIPathResolverFunc can resolve paths properly with the legacy API. +func LegacyAPIPathResolverFunc(kind schema.GroupVersionKind) string { + if len(kind.Group) == 0 { + return "/api" + } + return "/apis" +} + +// clientPoolImpl implements ClientPool and caches clients for the resource group versions +// is asked to retrieve. This type is thread safe. +type clientPoolImpl struct { + lock sync.RWMutex + config *restclient.Config + clients map[schema.GroupVersion]*Client + apiPathResolverFunc APIPathResolverFunc + mapper meta.RESTMapper +} + +// NewClientPool returns a ClientPool from the specified config. It reuses clients for the the same +// group version. It is expected this type may be wrapped by specific logic that special cases certain +// resources or groups. +func NewClientPool(config *restclient.Config, mapper meta.RESTMapper, apiPathResolverFunc APIPathResolverFunc) ClientPool { + confCopy := *config + + return &clientPoolImpl{ + config: &confCopy, + clients: map[schema.GroupVersion]*Client{}, + apiPathResolverFunc: apiPathResolverFunc, + mapper: mapper, + } +} + +// Instantiates a new dynamic client pool with the given config. +func NewDynamicClientPool(cfg *restclient.Config) ClientPool { + // restMapper is not needed when using LegacyAPIPathResolverFunc + emptyMapper := meta.MultiRESTMapper{} + return NewClientPool(cfg, emptyMapper, LegacyAPIPathResolverFunc) +} + +// ClientForGroupVersionResource uses the provided RESTMapper to identify the appropriate resource. Resource may +// be empty. If no matching kind is found the underlying client for that group is still returned. +func (c *clientPoolImpl) ClientForGroupVersionResource(resource schema.GroupVersionResource) (*Client, error) { + kinds, err := c.mapper.KindsFor(resource) + if err != nil { + if meta.IsNoMatchError(err) { + return c.ClientForGroupVersionKind(schema.GroupVersionKind{Group: resource.Group, Version: resource.Version}) + } + return nil, err + } + return c.ClientForGroupVersionKind(kinds[0]) +} + +// ClientForGroupVersion returns a client for the specified groupVersion, creates one if none exists. Kind +// in the GroupVersionKind may be empty. +func (c *clientPoolImpl) ClientForGroupVersionKind(kind schema.GroupVersionKind) (*Client, error) { + c.lock.Lock() + defer c.lock.Unlock() + + gv := kind.GroupVersion() + + // do we have a client already configured? + if existingClient, found := c.clients[gv]; found { + return existingClient, nil + } + + // avoid changing the original config + confCopy := *c.config + conf := &confCopy + + // we need to set the api path based on group version, if no group, default to legacy path + conf.APIPath = c.apiPathResolverFunc(kind) + + // we need to make a client + conf.GroupVersion = &gv + + dynamicClient, err := NewClient(conf) + if err != nil { + return nil, err + } + c.clients[gv] = dynamicClient + return dynamicClient, nil +} diff --git a/vendor/k8s.io/client-go/dynamic/dynamic_util.go b/vendor/k8s.io/client-go/dynamic/dynamic_util.go new file mode 100644 index 000000000..c834dbb0d --- /dev/null +++ b/vendor/k8s.io/client-go/dynamic/dynamic_util.go @@ -0,0 +1,96 @@ +/* +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 dynamic + +import ( + "fmt" + + "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" +) + +// VersionInterfaces provides an object converter and metadata +// accessor appropriate for use with unstructured objects. +func VersionInterfaces(schema.GroupVersion) (*meta.VersionInterfaces, error) { + return &meta.VersionInterfaces{ + ObjectConvertor: &unstructured.UnstructuredObjectConverter{}, + MetadataAccessor: meta.NewAccessor(), + }, nil +} + +// NewDiscoveryRESTMapper returns a RESTMapper based on discovery information. +func NewDiscoveryRESTMapper(resources []*metav1.APIResourceList, versionFunc meta.VersionInterfacesFunc) (*meta.DefaultRESTMapper, error) { + rm := meta.NewDefaultRESTMapper(nil, versionFunc) + for _, resourceList := range resources { + gv, err := schema.ParseGroupVersion(resourceList.GroupVersion) + if err != nil { + return nil, err + } + + for _, resource := range resourceList.APIResources { + gvk := gv.WithKind(resource.Kind) + scope := meta.RESTScopeRoot + if resource.Namespaced { + scope = meta.RESTScopeNamespace + } + rm.Add(gvk, scope) + } + } + return rm, nil +} + +// ObjectTyper provides an ObjectTyper implementation for +// unstructured.Unstructured object based on discovery information. +type ObjectTyper struct { + registered map[schema.GroupVersionKind]bool +} + +// NewObjectTyper constructs an ObjectTyper from discovery information. +func NewObjectTyper(resources []*metav1.APIResourceList) (runtime.ObjectTyper, error) { + ot := &ObjectTyper{registered: make(map[schema.GroupVersionKind]bool)} + for _, resourceList := range resources { + gv, err := schema.ParseGroupVersion(resourceList.GroupVersion) + if err != nil { + return nil, err + } + + for _, resource := range resourceList.APIResources { + ot.registered[gv.WithKind(resource.Kind)] = true + } + } + return ot, nil +} + +// ObjectKinds returns a slice of one element with the +// group,version,kind of the provided object, or an error if the +// object is not *unstructured.Unstructured or has no group,version,kind +// information. +func (ot *ObjectTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { + if _, ok := obj.(*unstructured.Unstructured); !ok { + return nil, false, fmt.Errorf("type %T is invalid for dynamic object typer", obj) + } + return []schema.GroupVersionKind{obj.GetObjectKind().GroupVersionKind()}, false, nil +} + +// Recognizes returns true if the provided group,version,kind was in +// the discovery information. +func (ot *ObjectTyper) Recognizes(gvk schema.GroupVersionKind) bool { + return ot.registered[gvk] +} From 92e1bbc1bf6415bf9fc4dc1bdba08d0331270a5c Mon Sep 17 00:00:00 2001 From: Daishan Peng Date: Wed, 8 Nov 2017 11:07:25 -0700 Subject: [PATCH 2/2] implement stub for cluster manager --- cluster/cluster.go | 277 ++++++++----------------------------- cmd/create.go | 72 +++++++++- cmd/remove.go | 10 +- {cluster => cmd}/types.go | 2 +- cmd/update.go | 7 +- cmd/utils.go | 162 ++++++++++++++++++++-- driver/gke/gke_driver.go | 94 +++++++++---- {cmd => plugin}/drivers.go | 8 +- stub/stub.go | 176 +++++++++++++++++++++++ stub/stub_test.go | 136 ++++++++++++++++++ 10 files changed, 683 insertions(+), 261 deletions(-) rename {cluster => cmd}/types.go (98%) rename {cmd => plugin}/drivers.go (74%) create mode 100644 stub/stub.go create mode 100644 stub/stub_test.go diff --git a/cluster/cluster.go b/cluster/cluster.go index a421e15a1..f08732b84 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -1,25 +1,8 @@ package cluster import ( - "encoding/base64" - "encoding/json" - "fmt" - "os" - "path/filepath" - rpcDriver "github.com/rancher/kontainer-engine/driver" - "github.com/rancher/kontainer-engine/utils" "github.com/sirupsen/logrus" - "github.com/urfave/cli" - "gopkg.in/yaml.v2" - "io/ioutil" -) - -const ( - caPem = "ca.pem" - clientKey = "key.pem" - clientCert = "cert.pem" - defaultConfigName = "config.json" ) // Cluster represents a kubernetes cluster @@ -54,7 +37,18 @@ type Cluster struct { // Metadata store specific driver options per cloud provider Metadata map[string]string - Ctx *cli.Context `json:"-"` + PersistStore PersistStore `json:"-,omitempty" yaml:"-,omitempty"` + + ConfigGetter ConfigGetter `json:"-,omitempty" yaml:"-,omitempty"` +} + +type PersistStore interface { + Check(name string) (bool, error) + Store(cluster Cluster) error +} + +type ConfigGetter interface { + GetConfig() (rpcDriver.DriverOptions, error) } // Driver defines how a cluster should be created and managed. @@ -87,33 +81,47 @@ type Driver interface { // Create creates a cluster func (c *Cluster) Create() error { - if c.IsCreated() { - logrus.Warnf("Cluster %s already exists. If it doesn't exist on the provider, make sure to clean them up by running `kontainer-engine rm %s`", c.Name, c.Name) + // check if it is already created + if ok, err := c.IsCreated(); err == nil && ok { + logrus.Warnf("Cluster %s already exists.", c.Name) return nil + } else if err != nil { + return err } - driverOpts := getDriverOpts(c.Ctx) - driverOpts.StringOptions["name"] = c.Name - err := c.Driver.SetDriverOptions(driverOpts) + + // get cluster config from cli flags or json config + driverOpts, err := c.ConfigGetter.GetConfig() if err != nil { return err } - if err := c.Driver.Create(); err != nil { + + // pass cluster config to rpc driver + if err := c.Driver.SetDriverOptions(driverOpts); err != nil { return err } - info, err := c.Driver.Get(c.Name) - if err != nil { + + // create cluster + if err := c.Driver.Create(); err != nil { return err } - transformClusterInfo(c, info) - if err := c.StoreConfig(); err != nil { + + // receive cluster info back + if info, err := c.Driver.Get(c.Name); err != nil { return err + } else { + transformClusterInfo(c, info) } + + // persist cluster info return c.Store() } // Update updates a cluster func (c *Cluster) Update() error { - driverOpts := getDriverOpts(c.Ctx) + driverOpts, err := c.ConfigGetter.GetConfig() + if err != nil { + return err + } driverOpts.StringOptions["name"] = c.Name for k, v := range c.Metadata { driverOpts.StringOptions[k] = v @@ -132,24 +140,6 @@ func (c *Cluster) Update() error { return c.Store() } -// Remove removes a cluster -func (c *Cluster) Remove() error { - driverOptions := rpcDriver.DriverOptions{ - BoolOptions: make(map[string]bool), - StringOptions: make(map[string]string), - IntOptions: make(map[string]int64), - StringSliceOptions: make(map[string]*rpcDriver.StringSlice), - } - for k, v := range c.Metadata { - driverOptions.StringOptions[k] = v - } - driverOptions.StringOptions["name"] = c.Name - if err := c.Driver.SetDriverOptions(driverOptions); err != nil { - return err - } - return c.Driver.Remove() -} - func transformClusterInfo(c *Cluster, clusterInfo rpcDriver.ClusterInfo) { c.ClientCertificate = clusterInfo.ClientCertificate c.ClientKey = clusterInfo.ClientKey @@ -163,191 +153,42 @@ func transformClusterInfo(c *Cluster, clusterInfo rpcDriver.ClusterInfo) { c.ServiceAccountToken = clusterInfo.ServiceAccountToken } -func (c *Cluster) IsCreated() bool { - if _, err := os.Stat(filepath.Join(c.GetFileDir(), defaultConfigName)); os.IsNotExist(err) { - return false - } - return true -} - -func (c *Cluster) GetFileDir() string { - return filepath.Join(utils.HomeDir(), "clusters", c.Name) -} - -// Store persists cluster information -func (c *Cluster) Store() error { - // todo: implement store logic to store the cluster info files. this might need to be a interface where we can store on disk or remote - for k, v := range map[string]string{ - c.RootCACert: caPem, - c.ClientKey: clientKey, - c.ClientCertificate: clientCert, - } { - data, err := base64.StdEncoding.DecodeString(k) - if err != nil { - return err - } - if err := utils.WriteToFile(data, filepath.Join(c.GetFileDir(), v)); err != nil { - return err - } - } - data, err := json.Marshal(c) +// Remove removes a cluster +func (c *Cluster) Remove() error { + driverOptions, err := c.ConfigGetter.GetConfig() if err != nil { return err } - return utils.WriteToFile(data, filepath.Join(c.GetFileDir(), defaultConfigName)) -} - -func (c *Cluster) StoreConfig() error { - isBasicOn := false - if c.Username != "" && c.Password != "" { - isBasicOn = true - } - username, password, token := "", "", "" - if isBasicOn { - username = c.Username - password = c.Password - } else { - token = c.ServiceAccountToken - } - - configFile := utils.KubeConfigFilePath() - config := KubeConfig{} - if _, err := os.Stat(configFile); err == nil { - data, err := ioutil.ReadFile(configFile) - if err != nil { - return err - } - if err := yaml.Unmarshal(data, &config); err != nil { - return err - } - } - config.APIVersion = "v1" - config.Kind = "Config" - - // setup clusters - cluster := ConfigCluster{ - Cluster: DataCluster{ - CertificateAuthorityData: string(c.RootCACert), - Server: fmt.Sprintf("https://%s", c.Endpoint), - }, - Name: c.Name, - } - if config.Clusters == nil || len(config.Clusters) == 0 { - config.Clusters = []ConfigCluster{cluster} - } else { - exist := false - for _, cluster := range config.Clusters { - if cluster.Name == c.Name { - exist = true - break - } - } - if !exist { - config.Clusters = append(config.Clusters, cluster) - } - } - - // setup users - user := ConfigUser{ - User: UserData{ - Username: username, - Password: password, - Token: token, - }, - Name: c.Name, - } - if config.Users == nil || len(config.Users) == 0 { - config.Users = []ConfigUser{user} - } else { - exist := false - for _, user := range config.Users { - if user.Name == c.Name { - exist = true - break - } - } - if !exist { - config.Users = append(config.Users, user) - } - } - - // setup context - context := ConfigContext{ - Context: ContextData{ - Cluster: c.Name, - User: c.Name, - }, - Name: c.Name, - } - if config.Contexts == nil || len(config.Contexts) == 0 { - config.Contexts = []ConfigContext{context} - } else { - exist := false - for _, context := range config.Contexts { - if context.Name == c.Name { - exist = true - break - } - } - if !exist { - config.Contexts = append(config.Contexts, context) - } - } - - data, err := yaml.Marshal(config) - if err != nil { - return err + for k, v := range c.Metadata { + driverOptions.StringOptions[k] = v } - fileToWrite := utils.KubeConfigFilePath() - if err := utils.WriteToFile(data, fileToWrite); err != nil { + driverOptions.StringOptions["name"] = c.Name + if err := c.Driver.SetDriverOptions(driverOptions); err != nil { return err } - logrus.Debugf("KubeConfig files is saved to %s", fileToWrite) - logrus.Debug("Kubeconfig file\n" + string(data)) + return c.Driver.Remove() +} + +func (c *Cluster) IsCreated() (bool, error) { + return c.PersistStore.Check(c.Name) +} - return nil +// Store persists cluster information +func (c *Cluster) Store() error { + return c.PersistStore.Store(*c) } // NewCluster create a cluster interface to do operations -func NewCluster(driverName string, ctx *cli.Context) (*Cluster, error) { - addr := ctx.String("plugin-listen-addr") +func NewCluster(driverName, addr, name string, configGetter ConfigGetter, persistStore PersistStore) (*Cluster, error) { rpcClient, err := rpcDriver.NewClient(driverName, addr) if err != nil { return nil, err } - name := "" - if ctx.NArg() > 0 { - name = ctx.Args().Get(0) - } return &Cluster{ - Driver: rpcClient, - DriverName: driverName, - Name: name, - Ctx: ctx, + Driver: rpcClient, + DriverName: driverName, + Name: name, + ConfigGetter: configGetter, + PersistStore: persistStore, }, nil } - -// getDriverOpts get the flags and value and generate DriverOptions -func getDriverOpts(ctx *cli.Context) rpcDriver.DriverOptions { - driverOptions := rpcDriver.DriverOptions{ - BoolOptions: make(map[string]bool), - StringOptions: make(map[string]string), - IntOptions: make(map[string]int64), - StringSliceOptions: make(map[string]*rpcDriver.StringSlice), - } - for _, flag := range ctx.Command.Flags { - switch flag.(type) { - case cli.StringFlag: - driverOptions.StringOptions[flag.GetName()] = ctx.String(flag.GetName()) - case cli.BoolFlag: - driverOptions.BoolOptions[flag.GetName()] = ctx.Bool(flag.GetName()) - case cli.Int64Flag: - driverOptions.IntOptions[flag.GetName()] = ctx.Int64(flag.GetName()) - case cli.StringSliceFlag: - driverOptions.StringSliceOptions[flag.GetName()] = &rpcDriver.StringSlice{ - Value: ctx.StringSlice(flag.GetName()), - } - } - } - return driverOptions -} diff --git a/cmd/create.go b/cmd/create.go index 015b96d07..02ca95a8f 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -1,15 +1,27 @@ package cmd import ( + "encoding/base64" + "encoding/json" "os" "strings" + "path/filepath" + "github.com/rancher/kontainer-engine/cluster" rpcDriver "github.com/rancher/kontainer-engine/driver" + "github.com/rancher/kontainer-engine/utils" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) +const ( + caPem = "ca.pem" + clientKey = "key.pem" + clientCert = "cert.pem" + defaultConfigName = "config.json" +) + var GlobalFlag = []cli.Flag{ cli.BoolFlag{ Name: "debug", @@ -95,13 +107,71 @@ func flagHackLookup(flagName string) string { return "" } +type cliConfigGetter struct { + name string + ctx *cli.Context +} + +func (c cliConfigGetter) GetConfig() (rpcDriver.DriverOptions, error) { + driverOpts := getDriverOpts(c.ctx) + driverOpts.StringOptions["name"] = c.name + return driverOpts, nil +} + +type cliPersistStore struct{} + +func (c cliPersistStore) Check(name string) (bool, error) { + path := filepath.Join(utils.HomeDir(), "clusters", name) + if _, err := os.Stat(filepath.Join(path, defaultConfigName)); os.IsNotExist(err) { + return false, nil + } + return true, nil +} + +func (c cliPersistStore) Store(cls cluster.Cluster) error { + // store kube config file + if err := storeConfig(cls); err != nil { + return err + } + // store json config file + fileDir := filepath.Join(utils.HomeDir(), "clusters", cls.Name) + for k, v := range map[string]string{ + cls.RootCACert: caPem, + cls.ClientKey: clientKey, + cls.ClientCertificate: clientCert, + } { + data, err := base64.StdEncoding.DecodeString(k) + if err != nil { + return err + } + if err := utils.WriteToFile(data, filepath.Join(fileDir, v)); err != nil { + return err + } + } + data, err := json.Marshal(cls) + if err != nil { + return err + } + return utils.WriteToFile(data, filepath.Join(fileDir, defaultConfigName)) +} + func create(ctx *cli.Context) error { driverName := ctx.String("driver") if driverName == "" { logrus.Error("Driver name is required") return cli.ShowCommandHelp(ctx, "create") } - cls, err := cluster.NewCluster(driverName, ctx) + addr := ctx.String("plugin-listen-addr") + name := "" + if ctx.NArg() > 0 { + name = ctx.Args().Get(0) + } + configGetter := cliConfigGetter{ + name: name, + ctx: ctx, + } + persistStore := cliPersistStore{} + cls, err := cluster.NewCluster(driverName, addr, name, configGetter, persistStore) if err != nil { return err } diff --git a/cmd/remove.go b/cmd/remove.go index aa92d3f83..0079da5db 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -8,6 +8,8 @@ import ( "github.com/rancher/kontainer-engine/store" "github.com/sirupsen/logrus" "github.com/urfave/cli" + "path/filepath" + "github.com/rancher/kontainer-engine/utils" ) func RmCommand() cli.Command { @@ -36,12 +38,18 @@ func rmCluster(ctx *cli.Context) error { if err != nil { return err } + configGetter := cliConfigGetter{ + name: name, + ctx: ctx, + } + cluster.ConfigGetter = configGetter + cluster.PersistStore = cliPersistStore{} cluster.Driver = rpcClient if err := cluster.Remove(); err != nil { return err } // todo: interface the storage level - clusterFilePath := cluster.GetFileDir() + clusterFilePath := filepath.Join(utils.HomeDir(), "clusters", cluster.Name) logrus.Debugf("Deleting cluster storage path %v", clusterFilePath) if err := os.RemoveAll(clusterFilePath); err != nil && !os.IsNotExist(err) { return err diff --git a/cluster/types.go b/cmd/types.go similarity index 98% rename from cluster/types.go rename to cmd/types.go index 319ad6245..70402dc72 100644 --- a/cluster/types.go +++ b/cmd/types.go @@ -1,4 +1,4 @@ -package cluster +package cmd type KubeConfig struct { APIVersion string `yaml:"apiVersion,omitempty"` diff --git a/cmd/update.go b/cmd/update.go index f3b3d133d..9c632f63a 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -77,7 +77,12 @@ func updateCluster(ctx *cli.Context) error { if err != nil { return err } - cluster.Ctx = ctx + configGetter := cliConfigGetter{ + name: name, + ctx: ctx, + } + cluster.ConfigGetter = configGetter + cluster.PersistStore = cliPersistStore{} cluster.Driver = rpcClient return cluster.Update() } diff --git a/cmd/utils.go b/cmd/utils.go index e1f85fcb8..05fe72019 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -5,15 +5,21 @@ import ( "github.com/rancher/kontainer-engine/cluster" generic "github.com/rancher/kontainer-engine/driver" + rpcDriver "github.com/rancher/kontainer-engine/driver" "github.com/rancher/kontainer-engine/utils" + "github.com/urfave/cli" yaml "gopkg.in/yaml.v2" + "github.com/sirupsen/logrus" + "os" + "fmt" + "github.com/rancher/kontainer-engine/plugin" ) // runRPCDriver runs the rpc server and returns func runRPCDriver(driverName string) (*generic.GrpcClient, string, error) { // addrChan is the channel to receive the server listen address addrChan := make(chan string) - runDriver(driverName, addrChan) + plugin.Run(driverName, addrChan) addr := <-addrChan rpcClient, err := generic.NewClient(driverName, addr) @@ -23,20 +29,20 @@ func runRPCDriver(driverName string) (*generic.GrpcClient, string, error) { return rpcClient, addr, nil } -func GetConfigFromFile() (cluster.KubeConfig, error) { +func GetConfigFromFile() (KubeConfig, error) { configFile := utils.KubeConfigFilePath() - config := cluster.KubeConfig{} + config := KubeConfig{} data, err := ioutil.ReadFile(configFile) if err != nil { - return cluster.KubeConfig{}, err + return KubeConfig{}, err } if err := yaml.Unmarshal(data, &config); err != nil { - return cluster.KubeConfig{}, err + return KubeConfig{}, err } return config, nil } -func SetConfigToFile(config cluster.KubeConfig) error { +func SetConfigToFile(config KubeConfig) error { data, err := yaml.Marshal(config) if err != nil { return err @@ -44,20 +50,20 @@ func SetConfigToFile(config cluster.KubeConfig) error { return utils.WriteToFile(data, utils.KubeConfigFilePath()) } -func deleteConfigByName(config *cluster.KubeConfig, name string) { - contexts := []cluster.ConfigContext{} +func deleteConfigByName(config *KubeConfig, name string) { + contexts := []ConfigContext{} for _, context := range config.Contexts { if context.Name != name { contexts = append(contexts, context) } } - clusters := []cluster.ConfigCluster{} + clusters := []ConfigCluster{} for _, cls := range config.Clusters { if cls.Name != name { clusters = append(clusters, cls) } } - users := []cluster.ConfigUser{} + users := []ConfigUser{} for _, user := range config.Users { if user.Name != name { users = append(users, user) @@ -67,3 +73,139 @@ func deleteConfigByName(config *cluster.KubeConfig, name string) { config.Clusters = clusters config.Users = users } + +// getDriverOpts get the flags and value and generate DriverOptions +func getDriverOpts(ctx *cli.Context) rpcDriver.DriverOptions { + driverOptions := rpcDriver.DriverOptions{ + BoolOptions: make(map[string]bool), + StringOptions: make(map[string]string), + IntOptions: make(map[string]int64), + StringSliceOptions: make(map[string]*rpcDriver.StringSlice), + } + for _, flag := range ctx.Command.Flags { + switch flag.(type) { + case cli.StringFlag: + driverOptions.StringOptions[flag.GetName()] = ctx.String(flag.GetName()) + case cli.BoolFlag: + driverOptions.BoolOptions[flag.GetName()] = ctx.Bool(flag.GetName()) + case cli.Int64Flag: + driverOptions.IntOptions[flag.GetName()] = ctx.Int64(flag.GetName()) + case cli.StringSliceFlag: + driverOptions.StringSliceOptions[flag.GetName()] = &rpcDriver.StringSlice{ + Value: ctx.StringSlice(flag.GetName()), + } + } + } + return driverOptions +} + +func storeConfig(c cluster.Cluster) error { + isBasicOn := false + if c.Username != "" && c.Password != "" { + isBasicOn = true + } + username, password, token := "", "", "" + if isBasicOn { + username = c.Username + password = c.Password + } else { + token = c.ServiceAccountToken + } + + configFile := utils.KubeConfigFilePath() + config := KubeConfig{} + if _, err := os.Stat(configFile); err == nil { + data, err := ioutil.ReadFile(configFile) + if err != nil { + return err + } + if err := yaml.Unmarshal(data, &config); err != nil { + return err + } + } + config.APIVersion = "v1" + config.Kind = "Config" + + // setup clusters + cluster := ConfigCluster{ + Cluster: DataCluster{ + CertificateAuthorityData: string(c.RootCACert), + Server: fmt.Sprintf("https://%s", c.Endpoint), + }, + Name: c.Name, + } + if config.Clusters == nil || len(config.Clusters) == 0 { + config.Clusters = []ConfigCluster{cluster} + } else { + exist := false + for _, cluster := range config.Clusters { + if cluster.Name == c.Name { + exist = true + break + } + } + if !exist { + config.Clusters = append(config.Clusters, cluster) + } + } + + // setup users + user := ConfigUser{ + User: UserData{ + Username: username, + Password: password, + Token: token, + }, + Name: c.Name, + } + if config.Users == nil || len(config.Users) == 0 { + config.Users = []ConfigUser{user} + } else { + exist := false + for _, user := range config.Users { + if user.Name == c.Name { + exist = true + break + } + } + if !exist { + config.Users = append(config.Users, user) + } + } + + // setup context + context := ConfigContext{ + Context: ContextData{ + Cluster: c.Name, + User: c.Name, + }, + Name: c.Name, + } + if config.Contexts == nil || len(config.Contexts) == 0 { + config.Contexts = []ConfigContext{context} + } else { + exist := false + for _, context := range config.Contexts { + if context.Name == c.Name { + exist = true + break + } + } + if !exist { + config.Contexts = append(config.Contexts, context) + } + } + + data, err := yaml.Marshal(config) + if err != nil { + return err + } + fileToWrite := utils.KubeConfigFilePath() + if err := utils.WriteToFile(data, fileToWrite); err != nil { + return err + } + logrus.Debugf("KubeConfig files is saved to %s", fileToWrite) + logrus.Debug("Kubeconfig file\n" + string(data)) + + return nil +} diff --git a/driver/gke/gke_driver.go b/driver/gke/gke_driver.go index 54d3c6f14..3417659f4 100644 --- a/driver/gke/gke_driver.go +++ b/driver/gke/gke_driver.go @@ -2,11 +2,9 @@ package gke import ( "strings" - "encoding/base64" "fmt" "time" - "os" generic "github.com/rancher/kontainer-engine/driver" @@ -45,6 +43,8 @@ type Driver struct { Description string // The number of nodes to create in this cluster InitialNodeCount int64 + // the initial kubernetes master version + InitialClusterVersion string // The authentication information for accessing the master MasterAuth *raw.MasterAuth // The name of this cluster @@ -104,6 +104,10 @@ func (d *Driver) GetDriverCreateOptions() (*generic.DriverFlags, error) { Type: generic.StringType, Usage: "An optional description of this cluster", } + driverFlag.Options["master-version"] = &generic.Flag{ + Type: generic.StringType, + Usage: "The kubernetes master version", + } driverFlag.Options["initial-node-count"] = &generic.Flag{ Type: generic.IntType, Usage: "The number of nodes to create in this cluster", @@ -147,35 +151,66 @@ func (d *Driver) GetDriverUpdateOptions() (*generic.DriverFlags, error) { } func (d *Driver) SetDriverOptions(driverOptions *generic.DriverOptions) error { - d.Name = driverOptions.StringOptions["name"] - d.ProjectID = driverOptions.StringOptions["projectId"] - d.Zone = driverOptions.StringOptions["zone"] - d.NodePoolID = driverOptions.StringOptions["nodePool"] - d.ClusterIpv4Cidr = driverOptions.StringOptions["cluster-ipv4-cidr"] - d.Description = driverOptions.StringOptions["description"] - d.NodeConfig.DiskSizeGb = driverOptions.IntOptions["disk-size-gb"] - d.NodeConfig.MachineType = driverOptions.StringOptions["machine-type"] - d.CredentialPath = driverOptions.StringOptions["gke-credential-path"] - d.EnableAlphaFeature = driverOptions.BoolOptions["enable-alpha-feature"] - if v, ok := driverOptions.IntOptions["initial-node-count"]; ok { - d.InitialNodeCount = v - } - if v, ok := driverOptions.StringSliceOptions["machine-type"]; ok { - for _, part := range v.Value { - kv := strings.Split(part, "=") - if len(kv) == 2 { - d.NodeConfig.Labels[kv[0]] = kv[1] - } + d.Name = getValueFromDriverOptions(driverOptions, generic.StringType, "name").(string) + d.ProjectID = getValueFromDriverOptions(driverOptions, generic.StringType, "projectId").(string) + d.Zone = getValueFromDriverOptions(driverOptions, generic.StringType, "zone").(string) + d.NodePoolID = getValueFromDriverOptions(driverOptions, generic.StringType, "nodePool").(string) + d.ClusterIpv4Cidr = getValueFromDriverOptions(driverOptions, generic.StringType, "cluster-ipv4-cidr", "clusterIpv4Cidr").(string) + d.Description = getValueFromDriverOptions(driverOptions, generic.StringType, "description").(string) + d.InitialClusterVersion = getValueFromDriverOptions(driverOptions, generic.StringType, "master-version", "masterVersion").(string) + d.NodeConfig.DiskSizeGb = getValueFromDriverOptions(driverOptions, generic.IntType, "disk-size-gb", "diskSizeGb").(int64) + d.NodeConfig.MachineType = getValueFromDriverOptions(driverOptions, generic.StringType, "machine-type", "machineType").(string) + d.CredentialPath = getValueFromDriverOptions(driverOptions, generic.StringType, "gke-credential-path", "gkeCredentialPath").(string) + d.EnableAlphaFeature = getValueFromDriverOptions(driverOptions, generic.BoolType, "enable-alpha-feature", "enableAlphaFeature").(bool) + d.InitialNodeCount = getValueFromDriverOptions(driverOptions, generic.IntType, "initial-node-count", "initialNodeCount").(int64) + labelValues := getValueFromDriverOptions(driverOptions, generic.StringSliceType, "labels").(*generic.StringSlice) + for _, part := range labelValues.Value { + kv := strings.Split(part, "=") + if len(kv) == 2 { + d.NodeConfig.Labels[kv[0]] = kv[1] } } - // updateConfig - d.UpdateConfig.NodeCount = driverOptions.IntOptions["node-count"] - d.UpdateConfig.MasterVersion = driverOptions.StringOptions["master-version"] - d.UpdateConfig.NodeVersion = driverOptions.StringOptions["node-version"] + d.UpdateConfig.NodeCount = getValueFromDriverOptions(driverOptions, generic.IntType, "node-count", "nodeCount").(int64) + d.UpdateConfig.MasterVersion = getValueFromDriverOptions(driverOptions, generic.StringType, "master-version", "masterVersion").(string) + d.UpdateConfig.NodeVersion = getValueFromDriverOptions(driverOptions, generic.StringType, "node-version", "nodeVersion").(string) return d.validate() } +func getValueFromDriverOptions(driverOptions *generic.DriverOptions, optionType string, keys... string) interface{} { + switch optionType { + case generic.IntType: + for _, key := range keys { + if value, ok := driverOptions.IntOptions[key]; ok { + return value + } + } + return int64(0) + case generic.StringType: + for _, key := range keys { + if value, ok := driverOptions.StringOptions[key]; ok { + return value + } + } + return "" + case generic.BoolType: + for _, key := range keys { + if value, ok := driverOptions.BoolOptions[key]; ok { + return value + } + } + return false + case generic.StringSliceType: + for _, key := range keys { + if value, ok := driverOptions.StringSliceOptions[key]; ok { + return value + } + } + return &generic.StringSlice{} + } + return nil +} + func (d *Driver) validate() error { if d.ProjectID == "" || d.Zone == "" || d.Name == "" { logrus.Errorf("ProjectID or Zone or Name is required") @@ -206,6 +241,14 @@ func (d *Driver) Update() error { return err } logrus.Debugf("Received Update config %v", d.UpdateConfig) + if d.NodePoolID == "" { + cluster, err := svc.Projects.Zones.Clusters.Get(d.ProjectID, d.Zone, d.Name).Context(context.Background()).Do() + if err != nil { + return err + } + d.NodePoolID = cluster.NodePools[0].Name + } + if d.UpdateConfig.MasterVersion != "" { logrus.Infof("Updating master to %v", d.UpdateConfig.MasterVersion) operation, err := svc.Projects.Zones.Clusters.Update(d.ProjectID, d.Zone, d.Name, &raw.UpdateClusterRequest{ @@ -255,6 +298,7 @@ func (d *Driver) generateClusterCreateRequest() *raw.CreateClusterRequest { } request.Cluster.Name = d.Name request.Cluster.Zone = d.Zone + request.Cluster.InitialClusterVersion = d.InitialClusterVersion request.Cluster.InitialNodeCount = d.InitialNodeCount request.Cluster.ClusterIpv4Cidr = d.ClusterIpv4Cidr request.Cluster.Description = d.Description diff --git a/cmd/drivers.go b/plugin/drivers.go similarity index 74% rename from cmd/drivers.go rename to plugin/drivers.go index bb84869e3..6b4344541 100644 --- a/cmd/drivers.go +++ b/plugin/drivers.go @@ -1,4 +1,4 @@ -package cmd +package plugin import ( rpcDriver "github.com/rancher/kontainer-engine/driver" @@ -6,13 +6,13 @@ import ( ) var ( - builtInDrivers = map[string]bool{ + BuiltInDrivers = map[string]bool{ "gke": true, "aks": true, } ) -func runDriver(driverName string, addrChan chan string) error { +func Run(driverName string, addrChan chan string) error { var driver rpcDriver.Driver switch driverName { case "gke": @@ -20,7 +20,7 @@ func runDriver(driverName string, addrChan chan string) error { default: addrChan <- "" } - if builtInDrivers[driverName] { + if BuiltInDrivers[driverName] { go startRPCServer(rpcDriver.NewServer(driver, addrChan)) } return nil diff --git a/stub/stub.go b/stub/stub.go new file mode 100644 index 000000000..e5e92ea41 --- /dev/null +++ b/stub/stub.go @@ -0,0 +1,176 @@ +/* +This package can only be imported if it is running as a library. The init function will start all the driver plugin servers +*/ +package stub + +import ( + "encoding/json" + "fmt" + + "github.com/alena1108/cluster-controller/client/v1" + "github.com/rancher/kontainer-engine/cluster" + rpcDriver "github.com/rancher/kontainer-engine/driver" + "github.com/rancher/kontainer-engine/plugin" + "github.com/sirupsen/logrus" +) + +var ( + PluginAddress = map[string]string{} +) + +func init() { + go func() { + for driver := range plugin.BuiltInDrivers { + logrus.Infof("Activating driver %s", driver) + addr := make(chan string) + plugin.Run(driver, addr) + listenAddr := <-addr + PluginAddress[driver] = listenAddr + logrus.Infof("Activating driver %s done", driver) + } + }() +} + +type controllerConfigGetter struct { + driverName string + cluster v1.Cluster +} + +func (c controllerConfigGetter) GetConfig() (rpcDriver.DriverOptions, error) { + driverOptions := rpcDriver.DriverOptions{ + BoolOptions: make(map[string]bool), + StringOptions: make(map[string]string), + IntOptions: make(map[string]int64), + StringSliceOptions: make(map[string]*rpcDriver.StringSlice), + } + var config interface{} + switch c.driverName { + case "gke": + config = c.cluster.Spec.GKEConfig + case "aks": + config = c.cluster.Spec.AKSConfig + case "rke": + config = c.cluster.Spec.RKEConfig + } + opts, err := toMap(config) + if err != nil { + return driverOptions, err + } + flatten(opts, &driverOptions) + driverOptions.StringOptions["name"] = c.cluster.Name + + return driverOptions, nil +} + +// flatten take a map and flatten it and convert it into driverOptions +func flatten(data map[string]interface{}, driverOptions *rpcDriver.DriverOptions) { + for k, v := range data { + switch v.(type) { + case float64: + driverOptions.IntOptions[k] = int64(v.(float64)) + case string: + driverOptions.StringOptions[k] = v.(string) + case bool: + driverOptions.BoolOptions[k] = v.(bool) + case []string: + driverOptions.StringSliceOptions[k] = &rpcDriver.StringSlice{Value: v.([]string)} + case map[string]interface{}: + // hack for labels + if k == "labels" { + r := []string{} + for key1, value1 := range v.(map[string]interface{}) { + r = append(r, fmt.Sprintf("%v=%v", key1, value1)) + } + driverOptions.StringSliceOptions[k] = &rpcDriver.StringSlice{Value: r} + } else { + flatten(v.(map[string]interface{}), driverOptions) + } + } + } +} + +type controllerPersistStore struct {} + +// no-op +func (c controllerPersistStore) Check(name string) (bool, error) { + return false, nil +} + +// no-op +func (c controllerPersistStore) Store(cluster cluster.Cluster) error { + return nil +} + +func toMap(obj interface{}) (map[string]interface{}, error) { + data, err := json.Marshal(obj) + if err != nil { + return nil, err + } + var result map[string]interface{} + if err := json.Unmarshal(data, &result); err != nil { + return nil, err + } + return result, nil +} + +func convertCluster(cls v1.Cluster) (cluster.Cluster, error) { + // todo: decide whether we need a driver field + driverName := "" + if cls.Spec.AKSConfig != nil { + driverName = "aks" + } else if cls.Spec.GKEConfig != nil { + driverName = "gke" + } else if cls.Spec.RKEConfig != nil { + driverName = "rke" + } + if driverName == "" { + return cluster.Cluster{}, fmt.Errorf("no driver config found") + } + pluginAddr := PluginAddress[driverName] + configGetter := controllerConfigGetter{ + driverName: driverName, + cluster: cls, + } + persistStore := controllerPersistStore{} + clusterPlugin, err := cluster.NewCluster(driverName, pluginAddr, cls.Name, configGetter, persistStore) + if err != nil { + return cluster.Cluster{}, err + } + return *clusterPlugin, nil +} + +// stub for cluster manager to call +func Create(cluster v1.Cluster) (v1.Cluster, error) { + cls, err := convertCluster(cluster) + if err != nil { + return v1.Cluster{}, err + } + if err := cls.Create(); err != nil { + return v1.Cluster{}, err + } + if cluster.Status == nil { + cluster.Status = &v1.ClusterStatus{} + } + cluster.Status.APIEndpoint = fmt.Sprintf("http://%s", cls.Endpoint) + cluster.Status.ServiceAccountToken = cls.ServiceAccountToken + // todo: cacerts + return cluster, nil +} + +// stub for cluster manager to call +func Update(cluster v1.Cluster) error { + cls, err := convertCluster(cluster) + if err != nil { + return err + } + return cls.Update() +} + +// stub for cluster manager to call +func Remove(cluster v1.Cluster) error { + cls, err := convertCluster(cluster) + if err != nil { + return err + } + return cls.Remove() +} diff --git a/stub/stub_test.go b/stub/stub_test.go new file mode 100644 index 000000000..8962255d4 --- /dev/null +++ b/stub/stub_test.go @@ -0,0 +1,136 @@ +package stub + +import ( + "testing" + + "fmt" + "time" + + "github.com/alena1108/cluster-controller/client/v1" + rpcDriver "github.com/rancher/kontainer-engine/driver" + "gopkg.in/check.v1" +) + +// Hook up gocheck into the "go test" runner. +func Test(t *testing.T) { + check.TestingT(t) +} + +type StubTestSuite struct { +} + +var _ = check.Suite(&StubTestSuite{}) + +func (s *StubTestSuite) SetUpSuite(c *check.C) { +} + +func (s *StubTestSuite) TestFlatten(c *check.C) { + config := v1.GKEConfig{ + ProjectID: "test", + Zone: "test", + DiskSizeGb: 50, + Labels: map[string]string{ + "foo": "bar", + }, + EnableAlphaFeature: true, + } + config.UpdateConfig.MasterVersion = "1.7.1" + config.UpdateConfig.NodeVersion = "1.7.1" + config.UpdateConfig.NodeCount = 3 + opts, err := toMap(config) + if err != nil { + c.Fatal(err) + } + driverOptions := rpcDriver.DriverOptions{ + BoolOptions: make(map[string]bool), + StringOptions: make(map[string]string), + IntOptions: make(map[string]int64), + StringSliceOptions: make(map[string]*rpcDriver.StringSlice), + } + flatten(opts, &driverOptions) + fmt.Println(driverOptions) + boolResult := map[string]bool{ + "enableAlphaFeature": true, + } + stringResult := map[string]string{ + "projectId": "test", + "zone": "test", + "masterVersion": "1.7.1", + "nodeVersion": "1.7.1", + } + intResult := map[string]int64{ + "diskSizeGb": 50, + "nodeCount": 3, + } + stringSliceResult := map[string]rpcDriver.StringSlice{ + "labels": { + Value: []string{"foo=bar"}, + }, + } + c.Assert(driverOptions.BoolOptions, check.DeepEquals, boolResult) + c.Assert(driverOptions.IntOptions, check.DeepEquals, intResult) + c.Assert(driverOptions.StringOptions, check.DeepEquals, stringResult) + c.Assert(driverOptions.StringSliceOptions["labels"].Value, check.DeepEquals, stringSliceResult["labels"].Value) +} + +func (s *StubTestSuite) unTestCreate(c *check.C) { + time.Sleep(time.Second) + config := v1.GKEConfig{ + ProjectID: "rancher-dev", + Zone: "us-central1-a", + DiskSizeGb: 50, + Labels: map[string]string{ + "foo": "bar", + }, + EnableAlphaFeature: true, + } + cluster := v1.Cluster{} + cluster.Name = "daishan-test" + cluster.Spec.GKEConfig = &config + result, err := Create(cluster) + if err != nil { + c.Fatal(err) + } + fmt.Println(result) +} + +func (s *StubTestSuite) unTestUpdate(c *check.C) { + time.Sleep(time.Second) + config := v1.GKEConfig{ + ProjectID: "rancher-dev", + Zone: "us-central1-a", + DiskSizeGb: 50, + Labels: map[string]string{ + "foo": "bar", + }, + EnableAlphaFeature: true, + } + config.UpdateConfig.NodeCount = 4 + cluster := v1.Cluster{} + cluster.Name = "daishan-test" + cluster.Spec.GKEConfig = &config + err := Update(cluster) + if err != nil { + c.Fatal(err) + } +} + +func (s *StubTestSuite) unTestRemove(c *check.C) { + time.Sleep(time.Second) + config := v1.GKEConfig{ + ProjectID: "rancher-dev", + Zone: "us-central1-a", + DiskSizeGb: 50, + Labels: map[string]string{ + "foo": "bar", + }, + EnableAlphaFeature: true, + } + cluster := v1.Cluster{} + cluster.Name = "daishan-test" + cluster.Spec.GKEConfig = &config + err := Remove(cluster) + if err != nil { + c.Fatal(err) + } +}