diff --git a/Makefile b/Makefile index 58b368ac014..060475a495f 100644 --- a/Makefile +++ b/Makefile @@ -110,7 +110,8 @@ NON_VENDOR_DIRS = $(shell $(DOCKER_CMD) glide nv) ######################################################################### build: .init .generate_files \ $(BINDIR)/service-catalog \ - $(BINDIR)/user-broker + $(BINDIR)/user-broker \ + plugins user-broker: $(BINDIR)/user-broker $(BINDIR)/user-broker: .init contrib/cmd/user-broker \ @@ -385,3 +386,55 @@ release-push-%: $(MAKE) clean-bin $(MAKE) ARCH=$* build $(MAKE) ARCH=$* push + + +# kubectl plugin stuff +###################### +PLUGIN_EXES=binding broker class instance plan + +plugins: .init .generate_files \ + $(BINDIR)/binding/binding \ + $(BINDIR)/broker/broker \ + $(BINDIR)/class/class \ + $(BINDIR)/instance/instance \ + $(BINDIR)/plan/plan + +$(BINDIR)/binding/binding: \ + plugin/cmd/kubectl/binding/binding.go \ + plugin/cmd/kubectl/binding/plugin.yaml + rm -rf $(BINDIR)/binding + $(DOCKER_CMD) $(GO_BUILD) -o $@ $< + $(DOCKER_CMD) cp plugin/cmd/kubectl/binding/*yaml \ + $(BINDIR)/binding/ + +$(BINDIR)/broker/broker: \ + plugin/cmd/kubectl/broker/broker.go \ + plugin/cmd/kubectl/broker/plugin.yaml + rm -rf $(BINDIR)/broker + $(DOCKER_CMD) $(GO_BUILD) -o $@ $< + $(DOCKER_CMD) cp plugin/cmd/kubectl/broker/*yaml \ + $(BINDIR)/broker/ + +$(BINDIR)/class/class: \ + plugin/cmd/kubectl/class/class.go \ + plugin/cmd/kubectl/class/plugin.yaml + rm -rf $(BINDIR)/class + $(DOCKER_CMD) $(GO_BUILD) -o $@ $< + $(DOCKER_CMD) cp plugin/cmd/kubectl/class/*yaml \ + $(BINDIR)/class/ + +$(BINDIR)/instance/instance: \ + plugin/cmd/kubectl/instance/instance.go \ + plugin/cmd/kubectl/instance/plugin.yaml + rm -rf $(BINDIR)/instance + $(DOCKER_CMD) $(GO_BUILD) -o $@ $< + $(DOCKER_CMD) cp plugin/cmd/kubectl/instance/*yaml \ + $(BINDIR)/instance/ + +$(BINDIR)/plan/plan: \ + plugin/cmd/kubectl/plan/plan.go \ + plugin/cmd/kubectl/plan/plugin.yaml + rm -rf $(BINDIR)/plan + $(DOCKER_CMD) $(GO_BUILD) -o $@ $< + $(DOCKER_CMD) cp plugin/cmd/kubectl/plan/*yaml \ + $(BINDIR)/plan/ diff --git a/plugin/cmd/kubectl/binding/binding.go b/plugin/cmd/kubectl/binding/binding.go new file mode 100644 index 00000000000..35e08a45f6b --- /dev/null +++ b/plugin/cmd/kubectl/binding/binding.go @@ -0,0 +1,86 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + + "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/client" + "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/utils" +) + +const usage = `Usage: + kubectl plugin binding SUBCOMMAND + +Available subcommands: + list + get +` + +const listUsage = `Usage: + kubectl plugin binding list NAMESPACE +` + +const getUsage = `Usage: + kubectl plugin binding get NAMESPACE INSTANCE_NAME +` + +func main() { + if len(os.Args) < 2 { + utils.Exit1(usage) + } + + client, err := client.NewClient() + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to initialize service catalog client (%s)", err)) + } + if os.Args[1] == "list" { + if len(os.Args) != 3 { + utils.Exit1(listUsage) + } + namespace := os.Args[2] + bindings, err := client.ListBindings(namespace) + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to list bindings in namespace %s (%s)", namespace, err)) + } + + table := utils.NewTable("BINDING NAME", "NAMESPACE", "INSTANCE NAME") + for _, v := range bindings.Items { + table.AddRow(v.Name, v.Namespace, v.Spec.ServiceInstanceRef.Name) + } + err = table.Print() + if err != nil { + utils.Exit1(fmt.Sprintf("Error printing result (%s)", err)) + } + } else if os.Args[1] == "get" { + if len(os.Args) != 4 { + utils.Exit1(getUsage) + } + namespace := os.Args[2] + bindingName := os.Args[3] + binding, err := client.GetBinding(bindingName, namespace) + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to find binding %s in namespae %s (%s)", bindingName, namespace, err)) + } + table := utils.NewTable("BINDING NAME", "NAMESPACE", "INSTANCE NAME") + table.AddRow(binding.Name, binding.Namespace, binding.Spec.ServiceInstanceRef.Name) + err = table.Print() + } else { + utils.Exit1(usage) + } +} diff --git a/plugin/cmd/kubectl/binding/plugin.yaml b/plugin/cmd/kubectl/binding/plugin.yaml new file mode 100644 index 00000000000..7f206082aa9 --- /dev/null +++ b/plugin/cmd/kubectl/binding/plugin.yaml @@ -0,0 +1,10 @@ +name: "binding" +shortDesc: "This command interacts with Service Bindings" +command: "./binding" +tree: + - name: "list" + shortDesc: "Lists all Service Bindings in a particular namespace" + command: "./binding list" + - name: "get" + shortDesc: "Gets a particular Service Bindings in a particular namespace" + command: "./binding get" diff --git a/plugin/cmd/kubectl/broker/broker.go b/plugin/cmd/kubectl/broker/broker.go new file mode 100644 index 00000000000..1659ecd8bd7 --- /dev/null +++ b/plugin/cmd/kubectl/broker/broker.go @@ -0,0 +1,77 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + + "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/client" + "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/utils" +) + +const usage = `Usage: + kubectl plugin broker SUBCOMMAND + +Available subcommands: + list + get +` + +const getUsage = `Usage: + kubectl plugin broker get BROKER_NAME +` + +func main() { + if len(os.Args) < 2 { + utils.Exit1(usage) + } + + client, err := client.NewClient() + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to initialize service catalog client (%s)", err)) + } + if os.Args[1] == "list" { + brokers, err := client.ListBrokers() + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to list brokers (%s)", err)) + } + + table := utils.NewTable("BROKER NAME", "NAMESPACE", "URL") + for _, v := range brokers.Items { + table.AddRow(v.Name, v.Namespace, v.Spec.URL) + } + err = table.Print() + if err != nil { + utils.Exit1(fmt.Sprintf("Error printing result (%s)", err)) + } + } else if os.Args[1] == "get" { + if len(os.Args) != 3 { + utils.Exit1(getUsage) + } + brokerName := os.Args[2] + broker, err := client.GetBroker(brokerName) + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to find broker %s (%s)", brokerName, err)) + } + table := utils.NewTable("BROKER NAME", "NAMESPACE", "URL") + table.AddRow(broker.Name, broker.Namespace, broker.Spec.URL) + err = table.Print() + } else { + utils.Exit1(usage) + } +} diff --git a/plugin/cmd/kubectl/broker/plugin.yaml b/plugin/cmd/kubectl/broker/plugin.yaml new file mode 100644 index 00000000000..96bb0d78aa4 --- /dev/null +++ b/plugin/cmd/kubectl/broker/plugin.yaml @@ -0,0 +1,10 @@ +name: "broker" +shortDesc: "This command interacts with Cluster Service Brokers" +command: "./broker" +tree: + - name: "list" + shortDesc: "Lists all Cluster Service Brokers" + command: "./broker list" + - name: "get" + shortDesc: "Gets a particular Cluster Service Broker" + command: "./broker get" diff --git a/plugin/cmd/kubectl/class/class.go b/plugin/cmd/kubectl/class/class.go new file mode 100644 index 00000000000..47b393961f6 --- /dev/null +++ b/plugin/cmd/kubectl/class/class.go @@ -0,0 +1,77 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + + "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/client" + "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/utils" +) + +const usage = `Usage: + kubectl plugin class SUBCOMMAND + +Available subcommands: + list + get +` + +const getUsage = `Usage: + kubectl plugin class get CLASS_NAME +` + +func main() { + if len(os.Args) < 2 { + utils.Exit1(usage) + } + + client, err := client.NewClient() + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to initialize service catalog client (%s)", err)) + } + if os.Args[1] == "list" { + classes, err := client.ListClasses() + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to list classes (%s)", err)) + } + + table := utils.NewTable("CLASS NAME", "NAMESPACE", "BROKER NAME") + for _, v := range classes.Items { + table.AddRow(v.Name, v.Namespace, v.Spec.ClusterServiceBrokerName) + } + err = table.Print() + if err != nil { + utils.Exit1(fmt.Sprintf("Error printing result (%s)", err)) + } + } else if os.Args[1] == "get" { + if len(os.Args) != 3 { + utils.Exit1(getUsage) + } + className := os.Args[2] + class, err := client.GetClass(className) + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to find class %s (%s)", className, err)) + } + table := utils.NewTable("CLASS NAME", "NAMESPACE", "BROKER NAME") + table.AddRow(class.Name, class.Namespace, class.Spec.ClusterServiceBrokerName) + err = table.Print() + } else { + utils.Exit1(usage) + } +} diff --git a/plugin/cmd/kubectl/class/plugin.yaml b/plugin/cmd/kubectl/class/plugin.yaml new file mode 100644 index 00000000000..fa77c0d5c45 --- /dev/null +++ b/plugin/cmd/kubectl/class/plugin.yaml @@ -0,0 +1,10 @@ +name: "class" +shortDesc: "This command interacts with Cluster Service Classes" +command: "./class" +tree: + - name: "list" + shortDesc: "Lists all Cluster Service Classes" + command: "./class list" + - name: "get" + shortDesc: "Gets a particular Cluster Service Class" + command: "./class get" diff --git a/plugin/cmd/kubectl/instance/instance.go b/plugin/cmd/kubectl/instance/instance.go new file mode 100644 index 00000000000..e10dca70f83 --- /dev/null +++ b/plugin/cmd/kubectl/instance/instance.go @@ -0,0 +1,86 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + + "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/client" + "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/utils" +) + +const usage = `Usage: + kubectl plugin instance SUBCOMMAND + +Available subcommands: + list + get +` + +const listUsage = `Usage: + kubectl plugin instance list NAMESPACE +` + +const getUsage = `Usage: + kubectl plugin instance get NAMESPACE INSTANCE_NAME +` + +func main() { + if len(os.Args) < 2 { + utils.Exit1(usage) + } + + client, err := client.NewClient() + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to initialize service catalog client (%s)", err)) + } + if os.Args[1] == "list" { + if len(os.Args) != 3 { + utils.Exit1(listUsage) + } + namespace := os.Args[2] + instances, err := client.ListInstances(namespace) + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to list instances in namespace %s (%s)", namespace, err)) + } + + table := utils.NewTable("INSTANCE NAME", "NAMESPACE", "CLASS NAME", "PLAN NAME") + for _, v := range instances.Items { + table.AddRow(v.Name, v.Namespace, v.Spec.ClusterServiceClassRef.Name, v.Spec.ClusterServicePlanRef.Name) + } + err = table.Print() + if err != nil { + utils.Exit1(fmt.Sprintf("Error printing result (%s)", err)) + } + } else if os.Args[1] == "get" { + if len(os.Args) != 4 { + utils.Exit1(getUsage) + } + namespace := os.Args[2] + instanceName := os.Args[3] + instance, err := client.GetInstance(instanceName, namespace) + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to find instance %s in namespace %s (%s)", instanceName, namespace, err)) + } + table := utils.NewTable("INSTANCE NAME", "NAMESPACE", "CLASS NAME", "PLAN NAME") + table.AddRow(instance.Name, instance.Namespace, instance.Spec.ClusterServiceClassRef.Name, instance.Spec.ClusterServicePlanRef.Name) + err = table.Print() + } else { + utils.Exit1(usage) + } +} diff --git a/plugin/cmd/kubectl/instance/plugin.yaml b/plugin/cmd/kubectl/instance/plugin.yaml new file mode 100644 index 00000000000..8aab807e705 --- /dev/null +++ b/plugin/cmd/kubectl/instance/plugin.yaml @@ -0,0 +1,10 @@ +name: "instance" +shortDesc: "This command interacts with Service Instances" +command: "./instance" +tree: + - name: "list" + shortDesc: "Lists all Service Instances in a particular namespace" + command: "./instance list" + - name: "get" + shortDesc: "Gets a particular Service Instance in a particular namespace" + command: "./instance get" diff --git a/plugin/cmd/kubectl/plan/plan.go b/plugin/cmd/kubectl/plan/plan.go new file mode 100644 index 00000000000..8fd890af4a3 --- /dev/null +++ b/plugin/cmd/kubectl/plan/plan.go @@ -0,0 +1,77 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + + "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/client" + "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/utils" +) + +const usage = `Usage: + kubectl plugin plan SUBCOMMAND + +Available subcommands: + list + get +` + +const getUsage = `Usage: + kubectl plugin plan get PLAN_NAME +` + +func main() { + if len(os.Args) < 2 { + utils.Exit1(usage) + } + + client, err := client.NewClient() + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to initialize service catalog client (%s)", err)) + } + if os.Args[1] == "list" { + plans, err := client.ListPlans() + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to list plans (%s)", err)) + } + + table := utils.NewTable("PLAN NAME", "DESCRIPTION", "BROKER NAME") + for _, v := range plans.Items { + table.AddRow(v.Spec.ExternalName, v.Spec.Description, v.Spec.ClusterServiceBrokerName) + } + err = table.Print() + if err != nil { + utils.Exit1(fmt.Sprintf("Error printing result (%s)", err)) + } + } else if os.Args[1] == "get" { + if len(os.Args) != 3 { + utils.Exit1(getUsage) + } + planName := os.Args[2] + plan, err := client.GetPlan(planName) + if err != nil { + utils.Exit1(fmt.Sprintf("Unable to find plan %s (%s)", planName, err)) + } + table := utils.NewTable("PLAN NAME", "DESCRIPTIONS", "BROKER NAME") + table.AddRow(plan.Name, plan.Spec.Description, plan.Spec.ClusterServiceBrokerName) + err = table.Print() + } else { + utils.Exit1(usage) + } +} diff --git a/plugin/cmd/kubectl/plan/plugin.yaml b/plugin/cmd/kubectl/plan/plugin.yaml new file mode 100644 index 00000000000..2c67c04ea36 --- /dev/null +++ b/plugin/cmd/kubectl/plan/plugin.yaml @@ -0,0 +1,10 @@ +name: "plan" +shortDesc: "This command interacts with Cluster Service Plans" +command: "./plan" +tree: + - name: "list" + shortDesc: "Lists all Cluster Service Plans" + command: "./plan list" + - name: "get" + shortDesc: "Gets a particular Cluster Service Plan" + command: "./plan get" diff --git a/plugin/pkg/kubectl/client/assets/config b/plugin/pkg/kubectl/client/assets/config new file mode 100755 index 00000000000..b19682e031a --- /dev/null +++ b/plugin/pkg/kubectl/client/assets/config @@ -0,0 +1,20 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority: + server: https://notreal.com:1234 + name: local +contexts: +- context: + cluster: local + user: myself + name: local +current-context: local +kind: Config +preferences: {} +users: +- name: myself + user: + as-user-extra: {} + username: foo + password: bar diff --git a/plugin/pkg/kubectl/client/binding.go b/plugin/pkg/kubectl/client/binding.go new file mode 100644 index 00000000000..1cbbe3f0f6a --- /dev/null +++ b/plugin/pkg/kubectl/client/binding.go @@ -0,0 +1,34 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GetBinding retrieves a binding by external name in a given namespace +func (c *PluginClient) GetBinding(bindingName, namespace string) (*v1beta1.ServiceBinding, error) { + binding, err := c.ScClient.ServicecatalogV1beta1().ServiceBindings(namespace).Get(bindingName, v1.GetOptions{}) + return binding, err +} + +// ListBindings returns a list of all bindings in a namespace +func (c *PluginClient) ListBindings(namespace string) (*v1beta1.ServiceBindingList, error) { + bindings, err := c.ScClient.ServicecatalogV1beta1().ServiceBindings(namespace).List(v1.ListOptions{}) + return bindings, err +} diff --git a/plugin/pkg/kubectl/client/binding_test.go b/plugin/pkg/kubectl/client/binding_test.go new file mode 100644 index 00000000000..f25e1c19db1 --- /dev/null +++ b/plugin/pkg/kubectl/client/binding_test.go @@ -0,0 +1,107 @@ +/* +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 client_test + +import ( + "fmt" + + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/clientset/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/testing" + + . "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/client" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Binding", func() { + var ( + client *PluginClient + err error + svcCatClient *fake.Clientset + sb *v1beta1.ServiceBinding + sb2 *v1beta1.ServiceBinding + ) + + BeforeEach(func() { + client, err = NewClient() + Expect(err).NotTo(HaveOccurred()) + + sb = &v1beta1.ServiceBinding{ObjectMeta: metav1.ObjectMeta{Name: "foobar", Namespace: "foobar_namespace"}} + sb2 = &v1beta1.ServiceBinding{ObjectMeta: metav1.ObjectMeta{Name: "barbaz", Namespace: "foobar_namespace"}} + svcCatClient = fake.NewSimpleClientset(sb, sb2) + client.ScClient = svcCatClient + }) + + Describe("Get", func() { + It("Calls the generated v1beta1 List method with the passed in binding and namespace", func() { + bindingName := "foobar" + namespace := "foobar_namespace" + + binding, err := client.GetBinding(bindingName, namespace) + Expect(err).NotTo(HaveOccurred()) + Expect(binding.Name).To(Equal(bindingName)) + + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "servicebindings")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(bindingName)) + Expect(actions[0].(testing.GetActionImpl).Namespace).To(Equal(namespace)) + }) + It("Bubbles up errors", func() { + bindingName := "not_a_real_binding" + namespace := "foobar_namespace" + + _, err := client.GetBinding(bindingName, namespace) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("not found")) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "servicebindings")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(bindingName)) + Expect(actions[0].(testing.GetActionImpl).Namespace).To(Equal(namespace)) + }) + }) + + Describe("List", func() { + It("Calls the generated v1beta1 List method with the specified namespace", func() { + namespace := "foobar_namespace" + + bindings, err := client.ListBindings(namespace) + + Expect(err).NotTo(HaveOccurred()) + Expect(bindings.Items).Should(ConsistOf(*sb, *sb2)) + Expect(svcCatClient.Actions()[0].Matches("list", "servicebindings")).To(BeTrue()) + }) + It("Bubbles up errors", func() { + badClient := &fake.Clientset{} + errorMessage := "error retrieving list" + badClient.AddReactor("list", "servicebindings", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + client.ScClient = badClient + namespace := "foobar_namespace" + + _, err := client.ListBindings(namespace) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring(errorMessage)) + Expect(badClient.Actions()[0].Matches("list", "servicebindings")).To(BeTrue()) + }) + }) +}) diff --git a/plugin/pkg/kubectl/client/broker.go b/plugin/pkg/kubectl/client/broker.go new file mode 100644 index 00000000000..81dca517a3f --- /dev/null +++ b/plugin/pkg/kubectl/client/broker.go @@ -0,0 +1,34 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GetBroker retrieves a broker by external name +func (c *PluginClient) GetBroker(brokerName string) (*v1beta1.ClusterServiceBroker, error) { + broker, err := c.ScClient.ServicecatalogV1beta1().ClusterServiceBrokers().Get(brokerName, v1.GetOptions{}) + return broker, err +} + +// ListBrokers returns a list of all brokers +func (c *PluginClient) ListBrokers() (*v1beta1.ClusterServiceBrokerList, error) { + brokers, err := c.ScClient.ServicecatalogV1beta1().ClusterServiceBrokers().List(v1.ListOptions{}) + return brokers, err +} diff --git a/plugin/pkg/kubectl/client/broker_test.go b/plugin/pkg/kubectl/client/broker_test.go new file mode 100644 index 00000000000..6400071bf16 --- /dev/null +++ b/plugin/pkg/kubectl/client/broker_test.go @@ -0,0 +1,101 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client_test + +import ( + "fmt" + + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/clientset/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/testing" + + . "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/client" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Broker", func() { + var ( + client *PluginClient + err error + svcCatClient *fake.Clientset + sb *v1beta1.ClusterServiceBroker + sb2 *v1beta1.ClusterServiceBroker + ) + + BeforeEach(func() { + client, err = NewClient() + Expect(err).NotTo(HaveOccurred()) + + sb = &v1beta1.ClusterServiceBroker{ObjectMeta: metav1.ObjectMeta{Name: "foobar"}} + sb2 = &v1beta1.ClusterServiceBroker{ObjectMeta: metav1.ObjectMeta{Name: "barbaz"}} + svcCatClient = fake.NewSimpleClientset(sb, sb2) + client.ScClient = svcCatClient + }) + + Describe("Get", func() { + It("Calls the generated v1beta1 List method with the passed in broker", func() { + brokerName := "foobar" + + broker, err := client.GetBroker(brokerName) + + Expect(err).NotTo(HaveOccurred()) + Expect(broker.Name).To(Equal(brokerName)) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "clusterservicebrokers")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(brokerName)) + }) + It("Bubbles up errors", func() { + brokerName := "banana" + + broker, err := client.GetBroker(brokerName) + + Expect(broker).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("not found")) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "clusterservicebrokers")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(brokerName)) + }) + }) + + Describe("List", func() { + It("Calls the generated v1beta1 List method", func() { + brokers, err := client.ListBrokers() + + Expect(err).NotTo(HaveOccurred()) + Expect(brokers.Items).Should(ConsistOf(*sb, *sb2)) + Expect(svcCatClient.Actions()[0].Matches("list", "clusterservicebrokers")).To(BeTrue()) + }) + It("Bubbles up errors", func() { + badClient := &fake.Clientset{} + errorMessage := "error retrieving list" + badClient.AddReactor("list", "clusterservicebrokers", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + client.ScClient = badClient + _, err := client.ListBrokers() + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring(errorMessage)) + Expect(badClient.Actions()[0].Matches("list", "clusterservicebrokers")).To(BeTrue()) + }) + }) +}) diff --git a/plugin/pkg/kubectl/client/class.go b/plugin/pkg/kubectl/client/class.go new file mode 100644 index 00000000000..ec948072148 --- /dev/null +++ b/plugin/pkg/kubectl/client/class.go @@ -0,0 +1,34 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GetClass retrieves a class by external name +func (c *PluginClient) GetClass(className string) (*v1beta1.ClusterServiceClass, error) { + class, err := c.ScClient.ServicecatalogV1beta1().ClusterServiceClasses().Get(className, v1.GetOptions{}) + return class, err +} + +// ListClasses returns a list of all service classes +func (c *PluginClient) ListClasses() (*v1beta1.ClusterServiceClassList, error) { + classes, err := c.ScClient.ServicecatalogV1beta1().ClusterServiceClasses().List(v1.ListOptions{}) + return classes, err +} diff --git a/plugin/pkg/kubectl/client/class_test.go b/plugin/pkg/kubectl/client/class_test.go new file mode 100644 index 00000000000..798b45704ce --- /dev/null +++ b/plugin/pkg/kubectl/client/class_test.go @@ -0,0 +1,101 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client_test + +import ( + "fmt" + + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/clientset/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/testing" + + . "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/client" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Class", func() { + var ( + client *PluginClient + err error + svcCatClient *fake.Clientset + sc *v1beta1.ClusterServiceClass + sc2 *v1beta1.ClusterServiceClass + ) + + BeforeEach(func() { + client, err = NewClient() + Expect(err).NotTo(HaveOccurred()) + + sc = &v1beta1.ClusterServiceClass{ObjectMeta: metav1.ObjectMeta{Name: "foobar"}} + sc2 = &v1beta1.ClusterServiceClass{ObjectMeta: metav1.ObjectMeta{Name: "barbaz"}} + svcCatClient = fake.NewSimpleClientset(sc, sc2) + client.ScClient = svcCatClient + }) + + Describe("Get", func() { + It("Calls the generated v1beta1 List method with the passed in class", func() { + className := "foobar" + class, err := client.GetClass(className) + + Expect(err).NotTo(HaveOccurred()) + Expect(class.Name).To(Equal(className)) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "clusterserviceclasses")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(className)) + }) + It("Bubbles up errors", func() { + className := "banana" + + class, err := client.GetClass(className) + + Expect(class).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("not found")) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "clusterserviceclasses")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(className)) + }) + }) + + Describe("List", func() { + It("Calls the generated v1beta1 List method", func() { + classes, err := client.ListClasses() + + Expect(err).NotTo(HaveOccurred()) + Expect(classes.Items).Should(ConsistOf(*sc, *sc2)) + Expect(svcCatClient.Actions()[0].Matches("list", "clusterserviceclasses")).To(BeTrue()) + }) + It("Bubbles up errors", func() { + badClient := &fake.Clientset{} + errorMessage := "error retrieving list" + badClient.AddReactor("list", "clusterserviceclasses", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + client.ScClient = badClient + + _, err := client.ListClasses() + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring(errorMessage)) + Expect(badClient.Actions()[0].Matches("list", "clusterserviceclasses")).To(BeTrue()) + }) + }) +}) diff --git a/plugin/pkg/kubectl/client/instance.go b/plugin/pkg/kubectl/client/instance.go new file mode 100644 index 00000000000..d5a89361fc3 --- /dev/null +++ b/plugin/pkg/kubectl/client/instance.go @@ -0,0 +1,34 @@ +/* +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 client + +import ( + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GetInstance retrieves a service instance by external name in a given namespace +func (c *PluginClient) GetInstance(instanceName, namespace string) (*v1beta1.ServiceInstance, error) { + instance, err := c.ScClient.ServicecatalogV1beta1().ServiceInstances(namespace).Get(instanceName, v1.GetOptions{}) + return instance, err +} + +// ListInstances returns all service instances in a given namespace +func (c *PluginClient) ListInstances(namespace string) (*v1beta1.ServiceInstanceList, error) { + instances, err := c.ScClient.ServicecatalogV1beta1().ServiceInstances(namespace).List(v1.ListOptions{}) + return instances, err +} diff --git a/plugin/pkg/kubectl/client/instance_test.go b/plugin/pkg/kubectl/client/instance_test.go new file mode 100644 index 00000000000..19ad17a1909 --- /dev/null +++ b/plugin/pkg/kubectl/client/instance_test.go @@ -0,0 +1,106 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client_test + +import ( + "fmt" + + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/clientset/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/testing" + + . "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/client" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Instances", func() { + var ( + client *PluginClient + err error + svcCatClient *fake.Clientset + si *v1beta1.ServiceInstance + si2 *v1beta1.ServiceInstance + ) + + BeforeEach(func() { + client, err = NewClient() + Expect(err).NotTo(HaveOccurred()) + si = &v1beta1.ServiceInstance{ObjectMeta: metav1.ObjectMeta{Name: "foobar", Namespace: "foobar_namespace"}} + si2 = &v1beta1.ServiceInstance{ObjectMeta: metav1.ObjectMeta{Name: "barbaz", Namespace: "foobar_namespace"}} + svcCatClient = fake.NewSimpleClientset(si, si2) + client.ScClient = svcCatClient + }) + + Describe("Get", func() { + It("Calls the generated v1beta1 List method with the passed in instance and namespace", func() { + instanceName := "foobar" + namespace := "foobar_namespace" + + instance, err := client.GetInstance(instanceName, namespace) + Expect(err).NotTo(HaveOccurred()) + Expect(instance.Name).To(Equal(instanceName)) + + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "serviceinstances")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(instanceName)) + Expect(actions[0].(testing.GetActionImpl).Namespace).To(Equal(namespace)) + }) + It("Bubbles up errors", func() { + instanceName := "not_real" + namespace := "foobar_namespace" + + _, err := client.GetInstance(instanceName, namespace) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("not found")) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "serviceinstances")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(instanceName)) + Expect(actions[0].(testing.GetActionImpl).Namespace).To(Equal(namespace)) + }) + }) + + Describe("List", func() { + It("Calls the generated v1beta1 List method with the specified namespace", func() { + namespace := "foobar_namespace" + + instances, err := client.ListInstances(namespace) + + Expect(err).NotTo(HaveOccurred()) + Expect(instances.Items).Should(ConsistOf(*si, *si2)) + Expect(svcCatClient.Actions()[0].Matches("list", "serviceinstances")).To(BeTrue()) + }) + It("Bubbles up errors", func() { + badClient := &fake.Clientset{} + errorMessage := "error retrieving list" + badClient.AddReactor("list", "serviceinstances", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + client.ScClient = badClient + namespace := "foobar_namespace" + + _, err := client.ListInstances(namespace) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring(errorMessage)) + Expect(badClient.Actions()[0].Matches("list", "serviceinstances")).To(BeTrue()) + }) + }) +}) diff --git a/plugin/pkg/kubectl/client/plan.go b/plugin/pkg/kubectl/client/plan.go new file mode 100644 index 00000000000..1441d3af417 --- /dev/null +++ b/plugin/pkg/kubectl/client/plan.go @@ -0,0 +1,34 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GetPlan retrieves a plan by external name +func (c *PluginClient) GetPlan(planName string) (*v1beta1.ClusterServicePlan, error) { + plan, err := c.ScClient.ServicecatalogV1beta1().ClusterServicePlans().Get(planName, v1.GetOptions{}) + return plan, err +} + +// ListPlans lists all service plans +func (c *PluginClient) ListPlans() (*v1beta1.ClusterServicePlanList, error) { + plans, err := c.ScClient.ServicecatalogV1beta1().ClusterServicePlans().List(v1.ListOptions{}) + return plans, err +} diff --git a/plugin/pkg/kubectl/client/plan_test.go b/plugin/pkg/kubectl/client/plan_test.go new file mode 100644 index 00000000000..e615a5cbe46 --- /dev/null +++ b/plugin/pkg/kubectl/client/plan_test.go @@ -0,0 +1,102 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client_test + +import ( + "fmt" + + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/clientset/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/testing" + + . "github.com/kubernetes-incubator/service-catalog/plugin/pkg/kubectl/client" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Plan", func() { + var ( + client *PluginClient + err error + svcCatClient *fake.Clientset + sp *v1beta1.ClusterServicePlan + sp2 *v1beta1.ClusterServicePlan + ) + + BeforeEach(func() { + client, err = NewClient() + Expect(err).NotTo(HaveOccurred()) + + sp = &v1beta1.ClusterServicePlan{ObjectMeta: metav1.ObjectMeta{Name: "foobar"}} + sp2 = &v1beta1.ClusterServicePlan{ObjectMeta: metav1.ObjectMeta{Name: "barbaz"}} + svcCatClient = fake.NewSimpleClientset(sp, sp2) + client.ScClient = svcCatClient + }) + + Describe("Get", func() { + It("Calls the generated v1beta1 List method with the passed in plan", func() { + planName := "foobar" + + plan, err := client.GetPlan(planName) + + Expect(err).NotTo(HaveOccurred()) + Expect(plan.Name).To(Equal(planName)) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "clusterserviceplans")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(planName)) + + }) + It("Bubbles up errors", func() { + planName := "not_real" + + plan, err := client.GetPlan(planName) + + Expect(plan).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("not found")) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "clusterserviceplans")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(planName)) + }) + }) + + Describe("List", func() { + It("Calls the generated v1beta1 List method", func() { + plans, err := client.ListPlans() + + Expect(err).NotTo(HaveOccurred()) + Expect(plans.Items).Should(ConsistOf(*sp, *sp2)) + Expect(svcCatClient.Actions()[0].Matches("list", "clusterserviceplans")).To(BeTrue()) + }) + It("Bubbles up errors", func() { + badClient := &fake.Clientset{} + errorMessage := "error retrieving list" + badClient.AddReactor("list", "clusterserviceplans", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + client.ScClient = badClient + _, err := client.ListPlans() + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring(errorMessage)) + Expect(badClient.Actions()[0].Matches("list", "clusterserviceplans")).To(BeTrue()) + }) + }) +}) diff --git a/plugin/pkg/kubectl/client/plugin_client.go b/plugin/pkg/kubectl/client/plugin_client.go new file mode 100644 index 00000000000..da341860dcb --- /dev/null +++ b/plugin/pkg/kubectl/client/plugin_client.go @@ -0,0 +1,192 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "encoding/json" + "fmt" + "os" + "time" + + restclient "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/clientset" +) + +// PluginClient is a client for interacting with the service catalog +// via generated clientset interface +type PluginClient struct { + ScClient clientset.Interface + Config *restclient.Config +} + +// NewClient uses the KUBECONFIG environment variable to create a new client +// based on an existing configuration +func NewClient() (*PluginClient, error) { + // resolve kubeconfig location, prioritizing the --config global flag, + // then the value of the KUBECONFIG env var (if any), and defaulting + // to ~/.kube/config as a last resort. + home := os.Getenv("HOME") + kubeconfig := home + "/.kube/config" + + kubeconfigEnv := os.Getenv("KUBECONFIG") + if len(kubeconfigEnv) > 0 { + kubeconfig = kubeconfigEnv + } + + configFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CONFIG") + kubeConfigFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_KUBECONFIG") + if len(configFile) > 0 { + kubeconfig = configFile + } else if len(kubeConfigFile) > 0 { + kubeconfig = kubeConfigFile + } + + if len(kubeconfig) == 0 { + return nil, fmt.Errorf("error iniializing client. The KUBECONFIG environment variable must be defined") + } + + clientConfig, _, err := clientFromConfig(kubeconfig) + if err != nil { + return nil, fmt.Errorf("error obtaining client configuration: %v", err) + } + + err = applyGlobalOptionsToConfig(clientConfig) + if err != nil { + return nil, fmt.Errorf("error processing global plugin options: %v", err) + } + + c, err := clientset.NewForConfig(clientConfig) + if err != nil { + return nil, fmt.Errorf("error obtaining a client from existing configuration: %v", err) + } + + pluginClient := PluginClient{c, clientConfig} + return &pluginClient, nil +} + +func clientFromConfig(path string) (*restclient.Config, string, error) { + if path == "-" { + cfg, err := restclient.InClusterConfig() + if err != nil { + return nil, "", fmt.Errorf("cluster config not available: %v", err) + } + return cfg, "", nil + } + + rules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: path} + credentials, err := rules.Load() + if err != nil { + return nil, "", fmt.Errorf("the provided credentials %q could not be loaded: %v", path, err) + } + + cfg := clientcmd.NewDefaultClientConfig(*credentials, &clientcmd.ConfigOverrides{}) + config, err := cfg.ClientConfig() + if err != nil { + return nil, "", fmt.Errorf("the provided credentials %q could not be used: %v", path, err) + } + + namespace, _, _ := cfg.Namespace() + return config, namespace, nil +} + +func applyGlobalOptionsToConfig(config *restclient.Config) error { + // impersonation config + impersonateUser := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_AS") + if len(impersonateUser) > 0 { + config.Impersonate.UserName = impersonateUser + } + + impersonateGroupRaw := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_AS_GROUP") + if len(impersonateGroupRaw) > 0 { + impersonateGroup := []string{} + err := json.Unmarshal([]byte(impersonateGroupRaw), &impersonateGroup) + if err != nil { + return fmt.Errorf("error parsing global option %q: %v", "--as-group", err) + } + if len(impersonateGroup) > 0 { + config.Impersonate.Groups = impersonateGroup + } + } + // tls config + + caFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CERTIFICATE_AUTHORITY") + if len(caFile) > 0 { + config.TLSClientConfig.CAFile = caFile + } + + clientCertFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLIENT_CERTIFICATE") + if len(clientCertFile) > 0 { + config.TLSClientConfig.CertFile = clientCertFile + } + + clientKey := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLIENT_KEY") + if len(clientKey) > 0 { + config.TLSClientConfig.KeyFile = clientKey + } + + // kubeconfig config + + cluster := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLUSTER") + if len(cluster) > 0 { + // TODO(jvallejo): figure out how to override kubeconfig options + } + + context := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CONTEXT") + if len(context) > 0 { + // TODO(jvallejo): figure out how to override kubeconfig options + } + + user := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_USER") + if len(user) > 0 { + // TODO(jvallejo): figure out how to override kubeconfig options + } + + // user / misc request config + + requestTimeout := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_REQUEST_TIMEOUT") + if len(requestTimeout) > 0 { + t, err := time.ParseDuration(requestTimeout) + if err != nil { + return fmt.Errorf("%v", err) + } + config.Timeout = t + } + + server := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_SERVER") + if len(server) > 0 { + config.ServerName = server + } + + token := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_TOKEN") + if len(token) > 0 { + config.BearerToken = token + } + + username := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_USERNAME") + if len(username) > 0 { + config.Username = username + } + + password := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_PASSWORD") + if len(password) > 0 { + config.Username = password + } + + return nil +} diff --git a/plugin/pkg/kubectl/client/plugin_client_suite_test.go b/plugin/pkg/kubectl/client/plugin_client_suite_test.go new file mode 100644 index 00000000000..442bbd8f2f6 --- /dev/null +++ b/plugin/pkg/kubectl/client/plugin_client_suite_test.go @@ -0,0 +1,35 @@ +/* +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 client_test + +import ( + "os" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestPluginClient(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "PluginClient Suite") +} + +var _ = BeforeEach(func() { + os.Setenv("KUBECTL_PLUGINS_GLOBAL_FLAG_KUBECONFIG", "assets/config") +}) diff --git a/plugin/pkg/kubectl/utils/table_printer.go b/plugin/pkg/kubectl/utils/table_printer.go new file mode 100644 index 00000000000..69492e99ce3 --- /dev/null +++ b/plugin/pkg/kubectl/utils/table_printer.go @@ -0,0 +1,67 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +import ( + "fmt" + "os" + "text/tabwriter" +) + +// Table defines a tabular output - obviously in table format +type Table struct { + headers []string + rows [][]string +} + +// NewTable creates a new table based on the passed in header names +func NewTable(headers ...string) *Table { + return &Table{ + headers: headers, + } +} + +// AddRow will append the specified row to the table +func (t *Table) AddRow(row ...string) { + t.rows = append(t.rows, row) +} + +// Print prints the table to the screen +func (t *Table) Print() error { + padding := 3 + + w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', 0) + + //Print header + printStr := "" + for _, h := range t.headers { + printStr = printStr + h + "\t" + } + fmt.Fprintln(w, printStr) + + //Print rows + for _, rows := range t.rows { + printStr = "" + for _, row := range rows { + printStr = printStr + row + "\t" + } + fmt.Fprintln(w, printStr) + } + fmt.Fprintln(w) + + return w.Flush() +} diff --git a/plugin/pkg/kubectl/utils/utils.go b/plugin/pkg/kubectl/utils/utils.go new file mode 100644 index 00000000000..cc5538eb6b9 --- /dev/null +++ b/plugin/pkg/kubectl/utils/utils.go @@ -0,0 +1,229 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + "time" + + restclient "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kclientset "k8s.io/client-go/kubernetes" + + "github.com/fatih/color" + "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/clientset" +) + +// NewClient uses the KUBECONFIG environment variable to create a new client +// based on an existing configuration +func NewClient() (*clientset.Clientset, *restclient.Config) { + // resolve kubeconfig location, prioritizing the --config global flag, + // then the value of the KUBECONFIG env var (if any), and defaulting + // to ~/.kube/config as a last resort. + home := os.Getenv("HOME") + if runtime.GOOS == "windows" { + home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + if home == "" { + home = os.Getenv("USERPROFILE") + } + } + kubeconfig := filepath.Join(home, ".kube", "config") + + kubeconfigEnv := os.Getenv("KUBECONFIG") + if len(kubeconfigEnv) > 0 { + kubeconfig = kubeconfigEnv + } + + configFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CONFIG") + kubeConfigFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_KUBECONFIG") + if len(configFile) > 0 { + kubeconfig = configFile + } else if len(kubeConfigFile) > 0 { + kubeconfig = kubeConfigFile + } + + if len(kubeconfig) == 0 { + Exit1(fmt.Sprintf("error iniializing client. The KUBECONFIG environment variable must be defined.")) + } + + clientConfig, _, err := clientFromConfig(kubeconfig) + if err != nil { + Exit1(fmt.Sprintf("error obtaining client configuration: %v", err)) + } + + applyGlobalOptionsToConfig(clientConfig) + + c, err := clientset.NewForConfig(clientConfig) + if err != nil { + Exit1(fmt.Sprintf("error obtaining a client from existing configuration: %v", err)) + } + + return c, clientConfig +} + +func applyGlobalOptionsToConfig(config *restclient.Config) { + // impersonation config + impersonateUser := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_AS") + if len(impersonateUser) > 0 { + config.Impersonate.UserName = impersonateUser + } + + impersonateGroup := []string{} + err := json.Unmarshal([]byte(os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_AS_GROUP")), &impersonateGroup) + if err != nil { + Exit1(fmt.Sprintf("error parsing global option %q: %v", "--as-group", err)) + } + if len(impersonateGroup) > 0 { + config.Impersonate.Groups = impersonateGroup + } + + // tls config + + caFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CERTIFICATE_AUTHORITY") + if len(caFile) > 0 { + config.TLSClientConfig.CAFile = caFile + } + + clientCertFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLIENT_CERTIFICATE") + if len(clientCertFile) > 0 { + config.TLSClientConfig.CertFile = clientCertFile + } + + clientKey := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLIENT_KEY") + if len(clientKey) > 0 { + config.TLSClientConfig.KeyFile = clientKey + } + + // kubeconfig config + + cluster := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLUSTER") + if len(cluster) > 0 { + // TODO(jvallejo): figure out how to override kubeconfig options + } + + context := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CONTEXT") + if len(context) > 0 { + // TODO(jvallejo): figure out how to override kubeconfig options + } + + user := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_USER") + if len(user) > 0 { + // TODO(jvallejo): figure out how to override kubeconfig options + } + + // user / misc request config + + requestTimeout := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_REQUEST_TIMEOUT") + if len(requestTimeout) > 0 { + t, err := time.ParseDuration(requestTimeout) + if err != nil { + Exit1(fmt.Sprintf("%v", err)) + } + config.Timeout = t + } + + server := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_SERVER") + if len(server) > 0 { + config.ServerName = server + } + + token := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_TOKEN") + if len(token) > 0 { + config.BearerToken = token + } + + username := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_USERNAME") + if len(username) > 0 { + config.Username = username + } + + password := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_PASSWORD") + if len(password) > 0 { + config.Username = password + } +} + +func clientFromConfig(path string) (*restclient.Config, string, error) { + if path == "-" { + cfg, err := restclient.InClusterConfig() + if err != nil { + return nil, "", fmt.Errorf("cluster config not available: %v", err) + } + return cfg, "", nil + } + + rules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: path} + credentials, err := rules.Load() + if err != nil { + return nil, "", fmt.Errorf("the provided credentials %q could not be loaded: %v", path, err) + } + + cfg := clientcmd.NewDefaultClientConfig(*credentials, &clientcmd.ConfigOverrides{}) + config, err := cfg.ClientConfig() + if err != nil { + return nil, "", fmt.Errorf("the provided credentials %q could not be used: %v", path, err) + } + + namespace, _, _ := cfg.Namespace() + return config, namespace, nil +} + +// Namespace will return the value of KUBECTL_PLUGINS_CURRENT_NAMESPACE env var +func Namespace() string { + return os.Getenv("KUBECTL_PLUGINS_CURRENT_NAMESPACE") +} + +//CheckNamespaceExists checks if a specified namespace exists +//the targeted cluster +func CheckNamespaceExists(ns string, config *restclient.Config) { + fmt.Printf("Looking up Namespace '%s'...\n", ns) + kubeClient, err := kclientset.NewForConfig(config) + if err != nil { + Exit1(fmt.Sprintf("%v", err)) + } + + if _, err := kubeClient.Core().Namespaces().Get(ns, metav1.GetOptions{}); err != nil { + Exit1(fmt.Sprintf("%v", err)) + } +} + +//Loglevel returns the loglevel value set by the calling +//kubectl plugin framework +func Loglevel() (flagName, flagValue string) { + kubeLoglevel := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_V") + otherLoglevel := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_LOGLEVEL") + if len(otherLoglevel) > 0 { + return "--loglevel", otherLoglevel + } + if len(kubeLoglevel) == 0 { + kubeLoglevel = "0" + } + return "--v", kubeLoglevel +} + +// Exit1 will print the specified error string to the screen and +// then stop the program, with an exit code of 1 +func Exit1(errStr string) { + color.Red("Error\n\n%s\n", errStr) + os.Exit(1) +} diff --git a/vendor/github.com/fatih/color/LICENSE.md b/vendor/github.com/fatih/color/LICENSE.md new file mode 100644 index 00000000000..25fdaf639df --- /dev/null +++ b/vendor/github.com/fatih/color/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Fatih Arslan + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/fatih/color/README.md b/vendor/github.com/fatih/color/README.md new file mode 100644 index 00000000000..0921f98a5f3 --- /dev/null +++ b/vendor/github.com/fatih/color/README.md @@ -0,0 +1,154 @@ +# Color [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/color) [![Build Status](http://img.shields.io/travis/fatih/color.svg?style=flat-square)](https://travis-ci.org/fatih/color) + + + +Color lets you use colorized outputs in terms of [ANSI Escape +Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It +has support for Windows too! The API can be used in several ways, pick one that +suits you. + + + +![Color](http://i.imgur.com/c1JI0lA.png) + + +## Install + +```bash +go get github.com/fatih/color +``` + +## Examples + +### Standard colors + +```go +// Print with default helper functions +color.Cyan("Prints text in cyan.") + +// A newline will be appended automatically +color.Blue("Prints %s in blue.", "text") + +// These are using the default foreground colors +color.Red("We have red") +color.Magenta("And many others ..") + +``` + +### Mix and reuse colors + +```go +// Create a new color object +c := color.New(color.FgCyan).Add(color.Underline) +c.Println("Prints cyan text with an underline.") + +// Or just add them to New() +d := color.New(color.FgCyan, color.Bold) +d.Printf("This prints bold cyan %s\n", "too!.") + +// Mix up foreground and background colors, create new mixes! +red := color.New(color.FgRed) + +boldRed := red.Add(color.Bold) +boldRed.Println("This will print text in bold red.") + +whiteBackground := red.Add(color.BgWhite) +whiteBackground.Println("Red text with white background.") +``` + +### Custom print functions (PrintFunc) + +```go +// Create a custom print function for convenience +red := color.New(color.FgRed).PrintfFunc() +red("Warning") +red("Error: %s", err) + +// Mix up multiple attributes +notice := color.New(color.Bold, color.FgGreen).PrintlnFunc() +notice("Don't forget this...") +``` + +### Insert into noncolor strings (SprintFunc) + +```go +// Create SprintXxx functions to mix strings with other non-colorized strings: +yellow := color.New(color.FgYellow).SprintFunc() +red := color.New(color.FgRed).SprintFunc() +fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error")) + +info := color.New(color.FgWhite, color.BgGreen).SprintFunc() +fmt.Printf("This %s rocks!\n", info("package")) + +// Use helper functions +fmt.Printf("This", color.RedString("warning"), "should be not neglected.") +fmt.Printf(color.GreenString("Info:"), "an important message." ) + +// Windows supported too! Just don't forget to change the output to color.Output +fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS")) +``` + +### Plug into existing code + +```go +// Use handy standard colors +color.Set(color.FgYellow) + +fmt.Println("Existing text will now be in yellow") +fmt.Printf("This one %s\n", "too") + +color.Unset() // Don't forget to unset + +// You can mix up parameters +color.Set(color.FgMagenta, color.Bold) +defer color.Unset() // Use it in your function + +fmt.Println("All text will now be bold magenta.") +``` + +### Disable color + +There might be a case where you want to disable color output (for example to +pipe the standard output of your app to somewhere else). `Color` has support to +disable colors both globally and for single color definition. For example +suppose you have a CLI app and a `--no-color` bool flag. You can easily disable +the color output with: + +```go + +var flagNoColor = flag.Bool("no-color", false, "Disable color output") + +if *flagNoColor { + color.NoColor = true // disables colorized output +} +``` + +It also has support for single color definitions (local). You can +disable/enable color output on the fly: + +```go +c := color.New(color.FgCyan) +c.Println("Prints cyan text") + +c.DisableColor() +c.Println("This is printed without any color") + +c.EnableColor() +c.Println("This prints again cyan...") +``` + +## Todo + +* Save/Return previous values +* Evaluate fmt.Formatter interface + + +## Credits + + * [Fatih Arslan](https://github.com/fatih) + * Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable) + +## License + +The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details + diff --git a/vendor/github.com/fatih/color/color.go b/vendor/github.com/fatih/color/color.go new file mode 100644 index 00000000000..e3e997284d0 --- /dev/null +++ b/vendor/github.com/fatih/color/color.go @@ -0,0 +1,402 @@ +package color + +import ( + "fmt" + "os" + "strconv" + "strings" + + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" +) + +// NoColor defines if the output is colorized or not. It's dynamically set to +// false or true based on the stdout's file descriptor referring to a terminal +// or not. This is a global option and affects all colors. For more control +// over each color block use the methods DisableColor() individually. +var NoColor = !isatty.IsTerminal(os.Stdout.Fd()) + +// Color defines a custom color object which is defined by SGR parameters. +type Color struct { + params []Attribute + noColor *bool +} + +// Attribute defines a single SGR Code +type Attribute int + +const escape = "\x1b" + +// Base attributes +const ( + Reset Attribute = iota + Bold + Faint + Italic + Underline + BlinkSlow + BlinkRapid + ReverseVideo + Concealed + CrossedOut +) + +// Foreground text colors +const ( + FgBlack Attribute = iota + 30 + FgRed + FgGreen + FgYellow + FgBlue + FgMagenta + FgCyan + FgWhite +) + +// Foreground Hi-Intensity text colors +const ( + FgHiBlack Attribute = iota + 90 + FgHiRed + FgHiGreen + FgHiYellow + FgHiBlue + FgHiMagenta + FgHiCyan + FgHiWhite +) + +// Background text colors +const ( + BgBlack Attribute = iota + 40 + BgRed + BgGreen + BgYellow + BgBlue + BgMagenta + BgCyan + BgWhite +) + +// Background Hi-Intensity text colors +const ( + BgHiBlack Attribute = iota + 100 + BgHiRed + BgHiGreen + BgHiYellow + BgHiBlue + BgHiMagenta + BgHiCyan + BgHiWhite +) + +// New returns a newly created color object. +func New(value ...Attribute) *Color { + c := &Color{params: make([]Attribute, 0)} + c.Add(value...) + return c +} + +// Set sets the given parameters immediately. It will change the color of +// output with the given SGR parameters until color.Unset() is called. +func Set(p ...Attribute) *Color { + c := New(p...) + c.Set() + return c +} + +// Unset resets all escape attributes and clears the output. Usually should +// be called after Set(). +func Unset() { + if NoColor { + return + } + + fmt.Fprintf(Output, "%s[%dm", escape, Reset) +} + +// Set sets the SGR sequence. +func (c *Color) Set() *Color { + if c.isNoColorSet() { + return c + } + + fmt.Fprintf(Output, c.format()) + return c +} + +func (c *Color) unset() { + if c.isNoColorSet() { + return + } + + Unset() +} + +// Add is used to chain SGR parameters. Use as many as parameters to combine +// and create custom color objects. Example: Add(color.FgRed, color.Underline). +func (c *Color) Add(value ...Attribute) *Color { + c.params = append(c.params, value...) + return c +} + +func (c *Color) prepend(value Attribute) { + c.params = append(c.params, 0) + copy(c.params[1:], c.params[0:]) + c.params[0] = value +} + +// Output defines the standard output of the print functions. By default +// os.Stdout is used. +var Output = colorable.NewColorableStdout() + +// Print formats using the default formats for its operands and writes to +// standard output. Spaces are added between operands when neither is a +// string. It returns the number of bytes written and any write error +// encountered. This is the standard fmt.Print() method wrapped with the given +// color. +func (c *Color) Print(a ...interface{}) (n int, err error) { + c.Set() + defer c.unset() + + return fmt.Fprint(Output, a...) +} + +// Printf formats according to a format specifier and writes to standard output. +// It returns the number of bytes written and any write error encountered. +// This is the standard fmt.Printf() method wrapped with the given color. +func (c *Color) Printf(format string, a ...interface{}) (n int, err error) { + c.Set() + defer c.unset() + + return fmt.Fprintf(Output, format, a...) +} + +// Println formats using the default formats for its operands and writes to +// standard output. Spaces are always added between operands and a newline is +// appended. It returns the number of bytes written and any write error +// encountered. This is the standard fmt.Print() method wrapped with the given +// color. +func (c *Color) Println(a ...interface{}) (n int, err error) { + c.Set() + defer c.unset() + + return fmt.Fprintln(Output, a...) +} + +// PrintFunc returns a new function that prints the passed arguments as +// colorized with color.Print(). +func (c *Color) PrintFunc() func(a ...interface{}) { + return func(a ...interface{}) { c.Print(a...) } +} + +// PrintfFunc returns a new function that prints the passed arguments as +// colorized with color.Printf(). +func (c *Color) PrintfFunc() func(format string, a ...interface{}) { + return func(format string, a ...interface{}) { c.Printf(format, a...) } +} + +// PrintlnFunc returns a new function that prints the passed arguments as +// colorized with color.Println(). +func (c *Color) PrintlnFunc() func(a ...interface{}) { + return func(a ...interface{}) { c.Println(a...) } +} + +// SprintFunc returns a new function that returns colorized strings for the +// given arguments with fmt.Sprint(). Useful to put into or mix into other +// string. Windows users should use this in conjuction with color.Output, example: +// +// put := New(FgYellow).SprintFunc() +// fmt.Fprintf(color.Output, "This is a %s", put("warning")) +func (c *Color) SprintFunc() func(a ...interface{}) string { + return func(a ...interface{}) string { + return c.wrap(fmt.Sprint(a...)) + } +} + +// SprintfFunc returns a new function that returns colorized strings for the +// given arguments with fmt.Sprintf(). Useful to put into or mix into other +// string. Windows users should use this in conjuction with color.Output. +func (c *Color) SprintfFunc() func(format string, a ...interface{}) string { + return func(format string, a ...interface{}) string { + return c.wrap(fmt.Sprintf(format, a...)) + } +} + +// SprintlnFunc returns a new function that returns colorized strings for the +// given arguments with fmt.Sprintln(). Useful to put into or mix into other +// string. Windows users should use this in conjuction with color.Output. +func (c *Color) SprintlnFunc() func(a ...interface{}) string { + return func(a ...interface{}) string { + return c.wrap(fmt.Sprintln(a...)) + } +} + +// sequence returns a formated SGR sequence to be plugged into a "\x1b[...m" +// an example output might be: "1;36" -> bold cyan +func (c *Color) sequence() string { + format := make([]string, len(c.params)) + for i, v := range c.params { + format[i] = strconv.Itoa(int(v)) + } + + return strings.Join(format, ";") +} + +// wrap wraps the s string with the colors attributes. The string is ready to +// be printed. +func (c *Color) wrap(s string) string { + if c.isNoColorSet() { + return s + } + + return c.format() + s + c.unformat() +} + +func (c *Color) format() string { + return fmt.Sprintf("%s[%sm", escape, c.sequence()) +} + +func (c *Color) unformat() string { + return fmt.Sprintf("%s[%dm", escape, Reset) +} + +// DisableColor disables the color output. Useful to not change any existing +// code and still being able to output. Can be used for flags like +// "--no-color". To enable back use EnableColor() method. +func (c *Color) DisableColor() { + c.noColor = boolPtr(true) +} + +// EnableColor enables the color output. Use it in conjuction with +// DisableColor(). Otherwise this method has no side effects. +func (c *Color) EnableColor() { + c.noColor = boolPtr(false) +} + +func (c *Color) isNoColorSet() bool { + // check first if we have user setted action + if c.noColor != nil { + return *c.noColor + } + + // if not return the global option, which is disabled by default + return NoColor +} + +// Equals returns a boolean value indicating whether two colors are equal. +func (c *Color) Equals(c2 *Color) bool { + if len(c.params) != len(c2.params) { + return false + } + + for _, attr := range c.params { + if !c2.attrExists(attr) { + return false + } + } + + return true +} + +func (c *Color) attrExists(a Attribute) bool { + for _, attr := range c.params { + if attr == a { + return true + } + } + + return false +} + +func boolPtr(v bool) *bool { + return &v +} + +// Black is an convenient helper function to print with black foreground. A +// newline is appended to format by default. +func Black(format string, a ...interface{}) { printColor(format, FgBlack, a...) } + +// Red is an convenient helper function to print with red foreground. A +// newline is appended to format by default. +func Red(format string, a ...interface{}) { printColor(format, FgRed, a...) } + +// Green is an convenient helper function to print with green foreground. A +// newline is appended to format by default. +func Green(format string, a ...interface{}) { printColor(format, FgGreen, a...) } + +// Yellow is an convenient helper function to print with yellow foreground. +// A newline is appended to format by default. +func Yellow(format string, a ...interface{}) { printColor(format, FgYellow, a...) } + +// Blue is an convenient helper function to print with blue foreground. A +// newline is appended to format by default. +func Blue(format string, a ...interface{}) { printColor(format, FgBlue, a...) } + +// Magenta is an convenient helper function to print with magenta foreground. +// A newline is appended to format by default. +func Magenta(format string, a ...interface{}) { printColor(format, FgMagenta, a...) } + +// Cyan is an convenient helper function to print with cyan foreground. A +// newline is appended to format by default. +func Cyan(format string, a ...interface{}) { printColor(format, FgCyan, a...) } + +// White is an convenient helper function to print with white foreground. A +// newline is appended to format by default. +func White(format string, a ...interface{}) { printColor(format, FgWhite, a...) } + +func printColor(format string, p Attribute, a ...interface{}) { + if !strings.HasSuffix(format, "\n") { + format += "\n" + } + + c := &Color{params: []Attribute{p}} + c.Printf(format, a...) +} + +// BlackString is an convenient helper function to return a string with black +// foreground. +func BlackString(format string, a ...interface{}) string { + return New(FgBlack).SprintfFunc()(format, a...) +} + +// RedString is an convenient helper function to return a string with red +// foreground. +func RedString(format string, a ...interface{}) string { + return New(FgRed).SprintfFunc()(format, a...) +} + +// GreenString is an convenient helper function to return a string with green +// foreground. +func GreenString(format string, a ...interface{}) string { + return New(FgGreen).SprintfFunc()(format, a...) +} + +// YellowString is an convenient helper function to return a string with yellow +// foreground. +func YellowString(format string, a ...interface{}) string { + return New(FgYellow).SprintfFunc()(format, a...) +} + +// BlueString is an convenient helper function to return a string with blue +// foreground. +func BlueString(format string, a ...interface{}) string { + return New(FgBlue).SprintfFunc()(format, a...) +} + +// MagentaString is an convenient helper function to return a string with magenta +// foreground. +func MagentaString(format string, a ...interface{}) string { + return New(FgMagenta).SprintfFunc()(format, a...) +} + +// CyanString is an convenient helper function to return a string with cyan +// foreground. +func CyanString(format string, a ...interface{}) string { + return New(FgCyan).SprintfFunc()(format, a...) +} + +// WhiteString is an convenient helper function to return a string with white +// foreground. +func WhiteString(format string, a ...interface{}) string { + return New(FgWhite).SprintfFunc()(format, a...) +} diff --git a/vendor/github.com/fatih/color/color_test.go b/vendor/github.com/fatih/color/color_test.go new file mode 100644 index 00000000000..8028535841d --- /dev/null +++ b/vendor/github.com/fatih/color/color_test.go @@ -0,0 +1,226 @@ +package color + +import ( + "bytes" + "fmt" + "testing" + + "github.com/mattn/go-colorable" +) + +// Testing colors is kinda different. First we test for given colors and their +// escaped formatted results. Next we create some visual tests to be tested. +// Each visual test includes the color name to be compared. +func TestColor(t *testing.T) { + rb := new(bytes.Buffer) + Output = rb + + NoColor = false + + testColors := []struct { + text string + code Attribute + }{ + {text: "black", code: FgBlack}, + {text: "red", code: FgRed}, + {text: "green", code: FgGreen}, + {text: "yellow", code: FgYellow}, + {text: "blue", code: FgBlue}, + {text: "magent", code: FgMagenta}, + {text: "cyan", code: FgCyan}, + {text: "white", code: FgWhite}, + {text: "hblack", code: FgHiBlack}, + {text: "hred", code: FgHiRed}, + {text: "hgreen", code: FgHiGreen}, + {text: "hyellow", code: FgHiYellow}, + {text: "hblue", code: FgHiBlue}, + {text: "hmagent", code: FgHiMagenta}, + {text: "hcyan", code: FgHiCyan}, + {text: "hwhite", code: FgHiWhite}, + } + + for _, c := range testColors { + New(c.code).Print(c.text) + + line, _ := rb.ReadString('\n') + scannedLine := fmt.Sprintf("%q", line) + colored := fmt.Sprintf("\x1b[%dm%s\x1b[0m", c.code, c.text) + escapedForm := fmt.Sprintf("%q", colored) + + fmt.Printf("%s\t: %s\n", c.text, line) + + if scannedLine != escapedForm { + t.Errorf("Expecting %s, got '%s'\n", escapedForm, scannedLine) + } + } +} + +func TestColorEquals(t *testing.T) { + fgblack1 := New(FgBlack) + fgblack2 := New(FgBlack) + bgblack := New(BgBlack) + fgbgblack := New(FgBlack, BgBlack) + fgblackbgred := New(FgBlack, BgRed) + fgred := New(FgRed) + bgred := New(BgRed) + + if !fgblack1.Equals(fgblack2) { + t.Error("Two black colors are not equal") + } + + if fgblack1.Equals(bgblack) { + t.Error("Fg and bg black colors are equal") + } + + if fgblack1.Equals(fgbgblack) { + t.Error("Fg black equals fg/bg black color") + } + + if fgblack1.Equals(fgred) { + t.Error("Fg black equals Fg red") + } + + if fgblack1.Equals(bgred) { + t.Error("Fg black equals Bg red") + } + + if fgblack1.Equals(fgblackbgred) { + t.Error("Fg black equals fg black bg red") + } +} + +func TestNoColor(t *testing.T) { + rb := new(bytes.Buffer) + Output = rb + + testColors := []struct { + text string + code Attribute + }{ + {text: "black", code: FgBlack}, + {text: "red", code: FgRed}, + {text: "green", code: FgGreen}, + {text: "yellow", code: FgYellow}, + {text: "blue", code: FgBlue}, + {text: "magent", code: FgMagenta}, + {text: "cyan", code: FgCyan}, + {text: "white", code: FgWhite}, + {text: "hblack", code: FgHiBlack}, + {text: "hred", code: FgHiRed}, + {text: "hgreen", code: FgHiGreen}, + {text: "hyellow", code: FgHiYellow}, + {text: "hblue", code: FgHiBlue}, + {text: "hmagent", code: FgHiMagenta}, + {text: "hcyan", code: FgHiCyan}, + {text: "hwhite", code: FgHiWhite}, + } + + for _, c := range testColors { + p := New(c.code) + p.DisableColor() + p.Print(c.text) + + line, _ := rb.ReadString('\n') + if line != c.text { + t.Errorf("Expecting %s, got '%s'\n", c.text, line) + } + } + + // global check + NoColor = true + defer func() { + NoColor = false + }() + for _, c := range testColors { + p := New(c.code) + p.Print(c.text) + + line, _ := rb.ReadString('\n') + if line != c.text { + t.Errorf("Expecting %s, got '%s'\n", c.text, line) + } + } + +} + +func TestColorVisual(t *testing.T) { + // First Visual Test + Output = colorable.NewColorableStdout() + + New(FgRed).Printf("red\t") + New(BgRed).Print(" ") + New(FgRed, Bold).Println(" red") + + New(FgGreen).Printf("green\t") + New(BgGreen).Print(" ") + New(FgGreen, Bold).Println(" green") + + New(FgYellow).Printf("yellow\t") + New(BgYellow).Print(" ") + New(FgYellow, Bold).Println(" yellow") + + New(FgBlue).Printf("blue\t") + New(BgBlue).Print(" ") + New(FgBlue, Bold).Println(" blue") + + New(FgMagenta).Printf("magenta\t") + New(BgMagenta).Print(" ") + New(FgMagenta, Bold).Println(" magenta") + + New(FgCyan).Printf("cyan\t") + New(BgCyan).Print(" ") + New(FgCyan, Bold).Println(" cyan") + + New(FgWhite).Printf("white\t") + New(BgWhite).Print(" ") + New(FgWhite, Bold).Println(" white") + fmt.Println("") + + // Second Visual test + Black("black") + Red("red") + Green("green") + Yellow("yellow") + Blue("blue") + Magenta("magenta") + Cyan("cyan") + White("white") + + // Third visual test + fmt.Println() + Set(FgBlue) + fmt.Println("is this blue?") + Unset() + + Set(FgMagenta) + fmt.Println("and this magenta?") + Unset() + + // Fourth Visual test + fmt.Println() + blue := New(FgBlue).PrintlnFunc() + blue("blue text with custom print func") + + red := New(FgRed).PrintfFunc() + red("red text with a printf func: %d\n", 123) + + put := New(FgYellow).SprintFunc() + warn := New(FgRed).SprintFunc() + + fmt.Fprintf(Output, "this is a %s and this is %s.\n", put("warning"), warn("error")) + + info := New(FgWhite, BgGreen).SprintFunc() + fmt.Fprintf(Output, "this %s rocks!\n", info("package")) + + // Fifth Visual Test + fmt.Println() + + fmt.Fprintln(Output, BlackString("black")) + fmt.Fprintln(Output, RedString("red")) + fmt.Fprintln(Output, GreenString("green")) + fmt.Fprintln(Output, YellowString("yellow")) + fmt.Fprintln(Output, BlueString("blue")) + fmt.Fprintln(Output, MagentaString("magenta")) + fmt.Fprintln(Output, CyanString("cyan")) + fmt.Fprintln(Output, WhiteString("white")) +} diff --git a/vendor/github.com/fatih/color/doc.go b/vendor/github.com/fatih/color/doc.go new file mode 100644 index 00000000000..17908787c99 --- /dev/null +++ b/vendor/github.com/fatih/color/doc.go @@ -0,0 +1,114 @@ +/* +Package color is an ANSI color package to output colorized or SGR defined +output to the standard output. The API can be used in several way, pick one +that suits you. + +Use simple and default helper functions with predefined foreground colors: + + color.Cyan("Prints text in cyan.") + + // a newline will be appended automatically + color.Blue("Prints %s in blue.", "text") + + // More default foreground colors.. + color.Red("We have red") + color.Yellow("Yellow color too!") + color.Magenta("And many others ..") + +However there are times where custom color mixes are required. Below are some +examples to create custom color objects and use the print functions of each +separate color object. + + // Create a new color object + c := color.New(color.FgCyan).Add(color.Underline) + c.Println("Prints cyan text with an underline.") + + // Or just add them to New() + d := color.New(color.FgCyan, color.Bold) + d.Printf("This prints bold cyan %s\n", "too!.") + + + // Mix up foreground and background colors, create new mixes! + red := color.New(color.FgRed) + + boldRed := red.Add(color.Bold) + boldRed.Println("This will print text in bold red.") + + whiteBackground := red.Add(color.BgWhite) + whiteBackground.Println("Red text with White background.") + + +You can create PrintXxx functions to simplify even more: + + // Create a custom print function for convenient + red := color.New(color.FgRed).PrintfFunc() + red("warning") + red("error: %s", err) + + // Mix up multiple attributes + notice := color.New(color.Bold, color.FgGreen).PrintlnFunc() + notice("don't forget this...") + + +Or create SprintXxx functions to mix strings with other non-colorized strings: + + yellow := New(FgYellow).SprintFunc() + red := New(FgRed).SprintFunc() + + fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error")) + + info := New(FgWhite, BgGreen).SprintFunc() + fmt.Printf("this %s rocks!\n", info("package")) + +Windows support is enabled by default. All Print functions works as intended. +However only for color.SprintXXX functions, user should use fmt.FprintXXX and +set the output to color.Output: + + fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS")) + + info := New(FgWhite, BgGreen).SprintFunc() + fmt.Fprintf(color.Output, "this %s rocks!\n", info("package")) + +Using with existing code is possible. Just use the Set() method to set the +standard output to the given parameters. That way a rewrite of an existing +code is not required. + + // Use handy standard colors. + color.Set(color.FgYellow) + + fmt.Println("Existing text will be now in Yellow") + fmt.Printf("This one %s\n", "too") + + color.Unset() // don't forget to unset + + // You can mix up parameters + color.Set(color.FgMagenta, color.Bold) + defer color.Unset() // use it in your function + + fmt.Println("All text will be now bold magenta.") + +There might be a case where you want to disable color output (for example to +pipe the standard output of your app to somewhere else). `Color` has support to +disable colors both globally and for single color definition. For example +suppose you have a CLI app and a `--no-color` bool flag. You can easily disable +the color output with: + + var flagNoColor = flag.Bool("no-color", false, "Disable color output") + + if *flagNoColor { + color.NoColor = true // disables colorized output + } + +It also has support for single color definitions (local). You can +disable/enable color output on the fly: + + c := color.New(color.FgCyan) + c.Println("Prints cyan text") + + c.DisableColor() + c.Println("This is printed without any color") + + c.EnableColor() + c.Println("This prints again cyan...") +*/ +package color diff --git a/vendor/github.com/mattn/go-colorable/LICENSE b/vendor/github.com/mattn/go-colorable/LICENSE new file mode 100644 index 00000000000..91b5cef30eb --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Yasuhiro Matsumoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mattn/go-colorable/README.md b/vendor/github.com/mattn/go-colorable/README.md new file mode 100644 index 00000000000..e84226a7358 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/README.md @@ -0,0 +1,43 @@ +# go-colorable + +Colorable writer for windows. + +For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.) +This package is possible to handle escape sequence for ansi color on windows. + +## Too Bad! + +![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png) + + +## So Good! + +![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png) + +## Usage + +```go +logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true}) +logrus.SetOutput(colorable.NewColorableStdout()) + +logrus.Info("succeeded") +logrus.Warn("not correct") +logrus.Error("something error") +logrus.Fatal("panic") +``` + +You can compile above code on non-windows OSs. + +## Installation + +``` +$ go get github.com/mattn/go-colorable +``` + +# License + +MIT + +# Author + +Yasuhiro Matsumoto (a.k.a mattn) diff --git a/vendor/github.com/mattn/go-colorable/colorable_others.go b/vendor/github.com/mattn/go-colorable/colorable_others.go new file mode 100644 index 00000000000..52d6653b34b --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/colorable_others.go @@ -0,0 +1,24 @@ +// +build !windows + +package colorable + +import ( + "io" + "os" +) + +func NewColorable(file *os.File) io.Writer { + if file == nil { + panic("nil passed instead of *os.File to NewColorable()") + } + + return file +} + +func NewColorableStdout() io.Writer { + return os.Stdout +} + +func NewColorableStderr() io.Writer { + return os.Stderr +} diff --git a/vendor/github.com/mattn/go-colorable/colorable_windows.go b/vendor/github.com/mattn/go-colorable/colorable_windows.go new file mode 100644 index 00000000000..22f075fd088 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/colorable_windows.go @@ -0,0 +1,809 @@ +package colorable + +import ( + "bytes" + "fmt" + "io" + "math" + "os" + "strconv" + "strings" + "syscall" + "unsafe" + + "github.com/mattn/go-isatty" +) + +const ( + foregroundBlue = 0x1 + foregroundGreen = 0x2 + foregroundRed = 0x4 + foregroundIntensity = 0x8 + foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) + backgroundBlue = 0x10 + backgroundGreen = 0x20 + backgroundRed = 0x40 + backgroundIntensity = 0x80 + backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) +) + +type wchar uint16 +type short int16 +type dword uint32 +type word uint16 + +type coord struct { + x short + y short +} + +type smallRect struct { + left short + top short + right short + bottom short +} + +type consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord +} + +type consoleCursorInfo struct { + size dword + visible int32 +} + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") + procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") + procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") + procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") + procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") + procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo") + procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo") +) + +type Writer struct { + out io.Writer + handle syscall.Handle + lastbuf bytes.Buffer + oldattr word +} + +func NewColorable(file *os.File) io.Writer { + if file == nil { + panic("nil passed instead of *os.File to NewColorable()") + } + + if isatty.IsTerminal(file.Fd()) { + var csbi consoleScreenBufferInfo + handle := syscall.Handle(file.Fd()) + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + return &Writer{out: file, handle: handle, oldattr: csbi.attributes} + } else { + return file + } +} + +func NewColorableStdout() io.Writer { + return NewColorable(os.Stdout) +} + +func NewColorableStderr() io.Writer { + return NewColorable(os.Stderr) +} + +var color256 = map[int]int{ + 0: 0x000000, + 1: 0x800000, + 2: 0x008000, + 3: 0x808000, + 4: 0x000080, + 5: 0x800080, + 6: 0x008080, + 7: 0xc0c0c0, + 8: 0x808080, + 9: 0xff0000, + 10: 0x00ff00, + 11: 0xffff00, + 12: 0x0000ff, + 13: 0xff00ff, + 14: 0x00ffff, + 15: 0xffffff, + 16: 0x000000, + 17: 0x00005f, + 18: 0x000087, + 19: 0x0000af, + 20: 0x0000d7, + 21: 0x0000ff, + 22: 0x005f00, + 23: 0x005f5f, + 24: 0x005f87, + 25: 0x005faf, + 26: 0x005fd7, + 27: 0x005fff, + 28: 0x008700, + 29: 0x00875f, + 30: 0x008787, + 31: 0x0087af, + 32: 0x0087d7, + 33: 0x0087ff, + 34: 0x00af00, + 35: 0x00af5f, + 36: 0x00af87, + 37: 0x00afaf, + 38: 0x00afd7, + 39: 0x00afff, + 40: 0x00d700, + 41: 0x00d75f, + 42: 0x00d787, + 43: 0x00d7af, + 44: 0x00d7d7, + 45: 0x00d7ff, + 46: 0x00ff00, + 47: 0x00ff5f, + 48: 0x00ff87, + 49: 0x00ffaf, + 50: 0x00ffd7, + 51: 0x00ffff, + 52: 0x5f0000, + 53: 0x5f005f, + 54: 0x5f0087, + 55: 0x5f00af, + 56: 0x5f00d7, + 57: 0x5f00ff, + 58: 0x5f5f00, + 59: 0x5f5f5f, + 60: 0x5f5f87, + 61: 0x5f5faf, + 62: 0x5f5fd7, + 63: 0x5f5fff, + 64: 0x5f8700, + 65: 0x5f875f, + 66: 0x5f8787, + 67: 0x5f87af, + 68: 0x5f87d7, + 69: 0x5f87ff, + 70: 0x5faf00, + 71: 0x5faf5f, + 72: 0x5faf87, + 73: 0x5fafaf, + 74: 0x5fafd7, + 75: 0x5fafff, + 76: 0x5fd700, + 77: 0x5fd75f, + 78: 0x5fd787, + 79: 0x5fd7af, + 80: 0x5fd7d7, + 81: 0x5fd7ff, + 82: 0x5fff00, + 83: 0x5fff5f, + 84: 0x5fff87, + 85: 0x5fffaf, + 86: 0x5fffd7, + 87: 0x5fffff, + 88: 0x870000, + 89: 0x87005f, + 90: 0x870087, + 91: 0x8700af, + 92: 0x8700d7, + 93: 0x8700ff, + 94: 0x875f00, + 95: 0x875f5f, + 96: 0x875f87, + 97: 0x875faf, + 98: 0x875fd7, + 99: 0x875fff, + 100: 0x878700, + 101: 0x87875f, + 102: 0x878787, + 103: 0x8787af, + 104: 0x8787d7, + 105: 0x8787ff, + 106: 0x87af00, + 107: 0x87af5f, + 108: 0x87af87, + 109: 0x87afaf, + 110: 0x87afd7, + 111: 0x87afff, + 112: 0x87d700, + 113: 0x87d75f, + 114: 0x87d787, + 115: 0x87d7af, + 116: 0x87d7d7, + 117: 0x87d7ff, + 118: 0x87ff00, + 119: 0x87ff5f, + 120: 0x87ff87, + 121: 0x87ffaf, + 122: 0x87ffd7, + 123: 0x87ffff, + 124: 0xaf0000, + 125: 0xaf005f, + 126: 0xaf0087, + 127: 0xaf00af, + 128: 0xaf00d7, + 129: 0xaf00ff, + 130: 0xaf5f00, + 131: 0xaf5f5f, + 132: 0xaf5f87, + 133: 0xaf5faf, + 134: 0xaf5fd7, + 135: 0xaf5fff, + 136: 0xaf8700, + 137: 0xaf875f, + 138: 0xaf8787, + 139: 0xaf87af, + 140: 0xaf87d7, + 141: 0xaf87ff, + 142: 0xafaf00, + 143: 0xafaf5f, + 144: 0xafaf87, + 145: 0xafafaf, + 146: 0xafafd7, + 147: 0xafafff, + 148: 0xafd700, + 149: 0xafd75f, + 150: 0xafd787, + 151: 0xafd7af, + 152: 0xafd7d7, + 153: 0xafd7ff, + 154: 0xafff00, + 155: 0xafff5f, + 156: 0xafff87, + 157: 0xafffaf, + 158: 0xafffd7, + 159: 0xafffff, + 160: 0xd70000, + 161: 0xd7005f, + 162: 0xd70087, + 163: 0xd700af, + 164: 0xd700d7, + 165: 0xd700ff, + 166: 0xd75f00, + 167: 0xd75f5f, + 168: 0xd75f87, + 169: 0xd75faf, + 170: 0xd75fd7, + 171: 0xd75fff, + 172: 0xd78700, + 173: 0xd7875f, + 174: 0xd78787, + 175: 0xd787af, + 176: 0xd787d7, + 177: 0xd787ff, + 178: 0xd7af00, + 179: 0xd7af5f, + 180: 0xd7af87, + 181: 0xd7afaf, + 182: 0xd7afd7, + 183: 0xd7afff, + 184: 0xd7d700, + 185: 0xd7d75f, + 186: 0xd7d787, + 187: 0xd7d7af, + 188: 0xd7d7d7, + 189: 0xd7d7ff, + 190: 0xd7ff00, + 191: 0xd7ff5f, + 192: 0xd7ff87, + 193: 0xd7ffaf, + 194: 0xd7ffd7, + 195: 0xd7ffff, + 196: 0xff0000, + 197: 0xff005f, + 198: 0xff0087, + 199: 0xff00af, + 200: 0xff00d7, + 201: 0xff00ff, + 202: 0xff5f00, + 203: 0xff5f5f, + 204: 0xff5f87, + 205: 0xff5faf, + 206: 0xff5fd7, + 207: 0xff5fff, + 208: 0xff8700, + 209: 0xff875f, + 210: 0xff8787, + 211: 0xff87af, + 212: 0xff87d7, + 213: 0xff87ff, + 214: 0xffaf00, + 215: 0xffaf5f, + 216: 0xffaf87, + 217: 0xffafaf, + 218: 0xffafd7, + 219: 0xffafff, + 220: 0xffd700, + 221: 0xffd75f, + 222: 0xffd787, + 223: 0xffd7af, + 224: 0xffd7d7, + 225: 0xffd7ff, + 226: 0xffff00, + 227: 0xffff5f, + 228: 0xffff87, + 229: 0xffffaf, + 230: 0xffffd7, + 231: 0xffffff, + 232: 0x080808, + 233: 0x121212, + 234: 0x1c1c1c, + 235: 0x262626, + 236: 0x303030, + 237: 0x3a3a3a, + 238: 0x444444, + 239: 0x4e4e4e, + 240: 0x585858, + 241: 0x626262, + 242: 0x6c6c6c, + 243: 0x767676, + 244: 0x808080, + 245: 0x8a8a8a, + 246: 0x949494, + 247: 0x9e9e9e, + 248: 0xa8a8a8, + 249: 0xb2b2b2, + 250: 0xbcbcbc, + 251: 0xc6c6c6, + 252: 0xd0d0d0, + 253: 0xdadada, + 254: 0xe4e4e4, + 255: 0xeeeeee, +} + +func (w *Writer) Write(data []byte) (n int, err error) { + var csbi consoleScreenBufferInfo + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + + er := bytes.NewBuffer(data) +loop: + for { + r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + if r1 == 0 { + break loop + } + + c1, _, err := er.ReadRune() + if err != nil { + break loop + } + if c1 != 0x1b { + fmt.Fprint(w.out, string(c1)) + continue + } + c2, _, err := er.ReadRune() + if err != nil { + w.lastbuf.WriteRune(c1) + break loop + } + if c2 != 0x5b { + w.lastbuf.WriteRune(c1) + w.lastbuf.WriteRune(c2) + continue + } + + var buf bytes.Buffer + var m rune + for { + c, _, err := er.ReadRune() + if err != nil { + w.lastbuf.WriteRune(c1) + w.lastbuf.WriteRune(c2) + w.lastbuf.Write(buf.Bytes()) + break loop + } + if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { + m = c + break + } + buf.Write([]byte(string(c))) + } + + var csbi consoleScreenBufferInfo + switch m { + case 'A': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.y -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'B': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.y += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'C': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'D': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + if n, err = strconv.Atoi(buf.String()); err == nil { + var csbi consoleScreenBufferInfo + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + } + case 'E': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = 0 + csbi.cursorPosition.y += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'F': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = 0 + csbi.cursorPosition.y -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'G': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = short(n-1) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'H': + token := strings.Split(buf.String(), ";") + if len(token) != 2 { + continue + } + n1, err := strconv.Atoi(token[0]) + if err != nil { + continue + } + n2, err := strconv.Atoi(token[1]) + if err != nil { + continue + } + csbi.cursorPosition.x = short(n2-1) + csbi.cursorPosition.y = short(n1-1) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'J': + n, err := strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + var cursor coord + switch n { + case 0: + cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} + case 1: + cursor = coord{x: csbi.window.left, y: csbi.window.top} + case 2: + cursor = coord{x: csbi.window.left, y: csbi.window.top} + } + var count, written dword + count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) + procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + case 'K': + n, err := strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + var cursor coord + switch n { + case 0: + cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} + case 1: + cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} + case 2: + cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} + } + var count, written dword + count = dword(csbi.size.x - csbi.cursorPosition.x) + procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + case 'm': + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + attr := csbi.attributes + cs := buf.String() + if cs == "" { + procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr)) + continue + } + token := strings.Split(cs, ";") + for i := 0; i < len(token); i++ { + ns := token[i] + if n, err = strconv.Atoi(ns); err == nil { + switch { + case n == 0 || n == 100: + attr = w.oldattr + case 1 <= n && n <= 5: + attr |= foregroundIntensity + case n == 7: + attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) + case 22 == n || n == 25 || n == 25: + attr |= foregroundIntensity + case n == 27: + attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) + case 30 <= n && n <= 37: + attr &= backgroundMask + if (n-30)&1 != 0 { + attr |= foregroundRed + } + if (n-30)&2 != 0 { + attr |= foregroundGreen + } + if (n-30)&4 != 0 { + attr |= foregroundBlue + } + case n == 38: // set foreground color. + if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") { + if n256, err := strconv.Atoi(token[i+2]); err == nil { + if n256foreAttr == nil { + n256setup() + } + attr &= backgroundMask + attr |= n256foreAttr[n256] + i += 2 + } + } else { + attr = attr & (w.oldattr & backgroundMask) + } + case n == 39: // reset foreground color. + attr &= backgroundMask + attr |= w.oldattr & foregroundMask + case 40 <= n && n <= 47: + attr &= foregroundMask + if (n-40)&1 != 0 { + attr |= backgroundRed + } + if (n-40)&2 != 0 { + attr |= backgroundGreen + } + if (n-40)&4 != 0 { + attr |= backgroundBlue + } + case n == 48: // set background color. + if i < len(token)-2 && token[i+1] == "5" { + if n256, err := strconv.Atoi(token[i+2]); err == nil { + if n256backAttr == nil { + n256setup() + } + attr &= foregroundMask + attr |= n256backAttr[n256] + i += 2 + } + } else { + attr = attr & (w.oldattr & foregroundMask) + } + case n == 49: // reset foreground color. + attr &= foregroundMask + attr |= w.oldattr & backgroundMask + case 90 <= n && n <= 97: + attr = (attr & backgroundMask) + attr |= foregroundIntensity + if (n-90)&1 != 0 { + attr |= foregroundRed + } + if (n-90)&2 != 0 { + attr |= foregroundGreen + } + if (n-90)&4 != 0 { + attr |= foregroundBlue + } + case 100 <= n && n <= 107: + attr = (attr & foregroundMask) + attr |= backgroundIntensity + if (n-100)&1 != 0 { + attr |= backgroundRed + } + if (n-100)&2 != 0 { + attr |= backgroundGreen + } + if (n-100)&4 != 0 { + attr |= backgroundBlue + } + } + procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr)) + } + } + case 'h': + cs := buf.String() + if cs == "?25" { + var ci consoleCursorInfo + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 1 + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + } + case 'l': + cs := buf.String() + if cs == "?25" { + var ci consoleCursorInfo + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 0 + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + } + } + } + return len(data) - w.lastbuf.Len(), nil +} + +type consoleColor struct { + rgb int + red bool + green bool + blue bool + intensity bool +} + +func (c consoleColor) foregroundAttr() (attr word) { + if c.red { + attr |= foregroundRed + } + if c.green { + attr |= foregroundGreen + } + if c.blue { + attr |= foregroundBlue + } + if c.intensity { + attr |= foregroundIntensity + } + return +} + +func (c consoleColor) backgroundAttr() (attr word) { + if c.red { + attr |= backgroundRed + } + if c.green { + attr |= backgroundGreen + } + if c.blue { + attr |= backgroundBlue + } + if c.intensity { + attr |= backgroundIntensity + } + return +} + +var color16 = []consoleColor{ + consoleColor{0x000000, false, false, false, false}, + consoleColor{0x000080, false, false, true, false}, + consoleColor{0x008000, false, true, false, false}, + consoleColor{0x008080, false, true, true, false}, + consoleColor{0x800000, true, false, false, false}, + consoleColor{0x800080, true, false, true, false}, + consoleColor{0x808000, true, true, false, false}, + consoleColor{0xc0c0c0, true, true, true, false}, + consoleColor{0x808080, false, false, false, true}, + consoleColor{0x0000ff, false, false, true, true}, + consoleColor{0x00ff00, false, true, false, true}, + consoleColor{0x00ffff, false, true, true, true}, + consoleColor{0xff0000, true, false, false, true}, + consoleColor{0xff00ff, true, false, true, true}, + consoleColor{0xffff00, true, true, false, true}, + consoleColor{0xffffff, true, true, true, true}, +} + +type hsv struct { + h, s, v float32 +} + +func (a hsv) dist(b hsv) float32 { + dh := a.h - b.h + switch { + case dh > 0.5: + dh = 1 - dh + case dh < -0.5: + dh = -1 - dh + } + ds := a.s - b.s + dv := a.v - b.v + return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv))) +} + +func toHSV(rgb int) hsv { + r, g, b := float32((rgb&0xFF0000)>>16)/256.0, + float32((rgb&0x00FF00)>>8)/256.0, + float32(rgb&0x0000FF)/256.0 + min, max := minmax3f(r, g, b) + h := max - min + if h > 0 { + if max == r { + h = (g - b) / h + if h < 0 { + h += 6 + } + } else if max == g { + h = 2 + (b-r)/h + } else { + h = 4 + (r-g)/h + } + } + h /= 6.0 + s := max - min + if max != 0 { + s /= max + } + v := max + return hsv{h: h, s: s, v: v} +} + +type hsvTable []hsv + +func toHSVTable(rgbTable []consoleColor) hsvTable { + t := make(hsvTable, len(rgbTable)) + for i, c := range rgbTable { + t[i] = toHSV(c.rgb) + } + return t +} + +func (t hsvTable) find(rgb int) consoleColor { + hsv := toHSV(rgb) + n := 7 + l := float32(5.0) + for i, p := range t { + d := hsv.dist(p) + if d < l { + l, n = d, i + } + } + return color16[n] +} + +func minmax3f(a, b, c float32) (min, max float32) { + if a < b { + if b < c { + return a, c + } else if a < c { + return a, b + } else { + return c, b + } + } else { + if a < c { + return b, c + } else if b < c { + return b, a + } else { + return c, a + } + } +} + +var n256foreAttr []word +var n256backAttr []word + +func n256setup() { + n256foreAttr = make([]word, 256) + n256backAttr = make([]word, 256) + t := toHSVTable(color16) + for i, rgb := range color256 { + c := t.find(rgb) + n256foreAttr[i] = c.foregroundAttr() + n256backAttr[i] = c.backgroundAttr() + } +} diff --git a/vendor/github.com/mattn/go-colorable/noncolorable.go b/vendor/github.com/mattn/go-colorable/noncolorable.go new file mode 100644 index 00000000000..fb976dbd8b7 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/noncolorable.go @@ -0,0 +1,57 @@ +package colorable + +import ( + "bytes" + "fmt" + "io" +) + +type NonColorable struct { + out io.Writer + lastbuf bytes.Buffer +} + +func NewNonColorable(w io.Writer) io.Writer { + return &NonColorable{out: w} +} + +func (w *NonColorable) Write(data []byte) (n int, err error) { + er := bytes.NewBuffer(data) +loop: + for { + c1, _, err := er.ReadRune() + if err != nil { + break loop + } + if c1 != 0x1b { + fmt.Fprint(w.out, string(c1)) + continue + } + c2, _, err := er.ReadRune() + if err != nil { + w.lastbuf.WriteRune(c1) + break loop + } + if c2 != 0x5b { + w.lastbuf.WriteRune(c1) + w.lastbuf.WriteRune(c2) + continue + } + + var buf bytes.Buffer + for { + c, _, err := er.ReadRune() + if err != nil { + w.lastbuf.WriteRune(c1) + w.lastbuf.WriteRune(c2) + w.lastbuf.Write(buf.Bytes()) + break loop + } + if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { + break + } + buf.Write([]byte(string(c))) + } + } + return len(data) - w.lastbuf.Len(), nil +} diff --git a/vendor/github.com/mattn/go-isatty/LICENSE b/vendor/github.com/mattn/go-isatty/LICENSE new file mode 100644 index 00000000000..65dc692b6b1 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/LICENSE @@ -0,0 +1,9 @@ +Copyright (c) Yasuhiro MATSUMOTO + +MIT License (Expat) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/mattn/go-isatty/README.md b/vendor/github.com/mattn/go-isatty/README.md new file mode 100644 index 00000000000..74845de4a24 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/README.md @@ -0,0 +1,37 @@ +# go-isatty + +isatty for golang + +## Usage + +```go +package main + +import ( + "fmt" + "github.com/mattn/go-isatty" + "os" +) + +func main() { + if isatty.IsTerminal(os.Stdout.Fd()) { + fmt.Println("Is Terminal") + } else { + fmt.Println("Is Not Terminal") + } +} +``` + +## Installation + +``` +$ go get github.com/mattn/go-isatty +``` + +# License + +MIT + +# Author + +Yasuhiro Matsumoto (a.k.a mattn) diff --git a/vendor/github.com/mattn/go-isatty/doc.go b/vendor/github.com/mattn/go-isatty/doc.go new file mode 100644 index 00000000000..17d4f90ebcc --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/doc.go @@ -0,0 +1,2 @@ +// Package isatty implements interface to isatty +package isatty diff --git a/vendor/github.com/mattn/go-isatty/isatty_appengine.go b/vendor/github.com/mattn/go-isatty/isatty_appengine.go new file mode 100644 index 00000000000..83c588773cf --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_appengine.go @@ -0,0 +1,9 @@ +// +build appengine + +package isatty + +// IsTerminal returns true if the file descriptor is terminal which +// is always false on on appengine classic which is a sandboxed PaaS. +func IsTerminal(fd uintptr) bool { + return false +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/mattn/go-isatty/isatty_bsd.go new file mode 100644 index 00000000000..42f2514d133 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_bsd.go @@ -0,0 +1,18 @@ +// +build darwin freebsd openbsd netbsd dragonfly +// +build !appengine + +package isatty + +import ( + "syscall" + "unsafe" +) + +const ioctlReadTermios = syscall.TIOCGETA + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_linux.go b/vendor/github.com/mattn/go-isatty/isatty_linux.go new file mode 100644 index 00000000000..9d24bac1db3 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_linux.go @@ -0,0 +1,18 @@ +// +build linux +// +build !appengine + +package isatty + +import ( + "syscall" + "unsafe" +) + +const ioctlReadTermios = syscall.TCGETS + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_solaris.go b/vendor/github.com/mattn/go-isatty/isatty_solaris.go new file mode 100644 index 00000000000..1f0c6bf53dc --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_solaris.go @@ -0,0 +1,16 @@ +// +build solaris +// +build !appengine + +package isatty + +import ( + "golang.org/x/sys/unix" +) + +// IsTerminal returns true if the given file descriptor is a terminal. +// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c +func IsTerminal(fd uintptr) bool { + var termio unix.Termio + err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) + return err == nil +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_windows.go b/vendor/github.com/mattn/go-isatty/isatty_windows.go new file mode 100644 index 00000000000..83c398b16db --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_windows.go @@ -0,0 +1,19 @@ +// +build windows +// +build !appengine + +package isatty + +import ( + "syscall" + "unsafe" +) + +var kernel32 = syscall.NewLazyDLL("kernel32.dll") +var procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +}