From ac4b85a4de60bd3e1d1710cb4c255556a09cf71b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Wed, 17 Apr 2019 15:01:06 +0200 Subject: [PATCH 1/2] feature(service describe): Output of service details As discussed in #48 this `kn service describe` as changed in this commit mimics `kubectl describe ` as that it output detail information in human readable output. Summary information of a list of services or a single services as well as exporting the service in various formats (JSON, YAML) is reserved to `kn service get` For this reason, the generic Printer handling from cli-runtime has been removed as `kn service describe` only outputs human readable format (that is btw also true for `kubectl` describe which does not allow for `-o` kind of options). This command shows information about the serivce itself, but also a summary about the associated revisions. "kn service describe" knows three modes, which can be combined: `--all` : Print more information. By default only are shorter summary is shown `--color` : Use a colorful output (but only when on a tty) `--hipster`: Experimental output mode for a very compact representation using emojis Of course there can be more and other information to be shown. Let's discuss and add this (and maybe remove the dense format) --- docs/cmd/kn_service.md | 2 +- docs/cmd/kn_service_describe.md | 14 +- go.mod | 2 + pkg/kn/commands/service/service_describe.go | 488 ++++++++- .../commands/service/service_describe_test.go | 54 +- pkg/kn/commands/service_describe_compact.go | 180 ++++ pkg/printers/prefixwriter.go | 86 ++ pkg/printers/tableprinter.go | 2 +- pkg/printers/tabwriter.go | 4 +- pkg/serving/client.go | 78 ++ vendor/github.com/fatih/color/.travis.yml | 5 + vendor/github.com/fatih/color/Gopkg.lock | 27 + vendor/github.com/fatih/color/Gopkg.toml | 30 + vendor/github.com/fatih/color/LICENSE.md | 20 + vendor/github.com/fatih/color/README.md | 179 ++++ vendor/github.com/fatih/color/color.go | 603 +++++++++++ vendor/github.com/fatih/color/doc.go | 133 +++ .../github.com/mattn/go-colorable/.travis.yml | 9 + vendor/github.com/mattn/go-colorable/LICENSE | 21 + .../github.com/mattn/go-colorable/README.md | 48 + .../mattn/go-colorable/colorable_appengine.go | 29 + .../mattn/go-colorable/colorable_others.go | 30 + .../mattn/go-colorable/colorable_windows.go | 980 ++++++++++++++++++ vendor/github.com/mattn/go-colorable/go.mod | 3 + vendor/github.com/mattn/go-colorable/go.sum | 4 + .../mattn/go-colorable/noncolorable.go | 55 + vendor/github.com/mattn/go-isatty/.travis.yml | 13 + vendor/github.com/mattn/go-isatty/LICENSE | 9 + vendor/github.com/mattn/go-isatty/README.md | 50 + vendor/github.com/mattn/go-isatty/doc.go | 2 + vendor/github.com/mattn/go-isatty/go.mod | 3 + vendor/github.com/mattn/go-isatty/go.sum | 2 + .../mattn/go-isatty/isatty_android.go | 23 + .../github.com/mattn/go-isatty/isatty_bsd.go | 24 + .../mattn/go-isatty/isatty_others.go | 15 + .../mattn/go-isatty/isatty_solaris.go | 22 + .../mattn/go-isatty/isatty_tcgets.go | 19 + .../mattn/go-isatty/isatty_windows.go | 94 ++ vendor/golang.org/x/sys/unix/syscall_linux.go | 14 + .../golang.org/x/sys/unix/ztypes_linux_386.go | 6 + .../x/sys/unix/ztypes_linux_amd64.go | 6 + .../golang.org/x/sys/unix/ztypes_linux_arm.go | 6 + .../x/sys/unix/ztypes_linux_arm64.go | 6 + .../x/sys/unix/ztypes_linux_mips.go | 6 + .../x/sys/unix/ztypes_linux_mips64.go | 6 + .../x/sys/unix/ztypes_linux_mips64le.go | 6 + .../x/sys/unix/ztypes_linux_mipsle.go | 6 + .../x/sys/unix/ztypes_linux_ppc64.go | 6 + .../x/sys/unix/ztypes_linux_ppc64le.go | 6 + .../x/sys/unix/ztypes_linux_riscv64.go | 6 + .../x/sys/unix/ztypes_linux_s390x.go | 6 + .../x/sys/unix/ztypes_linux_sparc64.go | 6 + .../x/sys/windows/security_windows.go | 2 +- vendor/modules.txt | 8 +- 54 files changed, 3421 insertions(+), 43 deletions(-) create mode 100644 pkg/kn/commands/service_describe_compact.go create mode 100644 pkg/printers/prefixwriter.go create mode 100644 pkg/serving/client.go create mode 100644 vendor/github.com/fatih/color/.travis.yml create mode 100644 vendor/github.com/fatih/color/Gopkg.lock create mode 100644 vendor/github.com/fatih/color/Gopkg.toml create mode 100644 vendor/github.com/fatih/color/LICENSE.md create mode 100644 vendor/github.com/fatih/color/README.md create mode 100644 vendor/github.com/fatih/color/color.go create mode 100644 vendor/github.com/fatih/color/doc.go create mode 100644 vendor/github.com/mattn/go-colorable/.travis.yml create mode 100644 vendor/github.com/mattn/go-colorable/LICENSE create mode 100644 vendor/github.com/mattn/go-colorable/README.md create mode 100644 vendor/github.com/mattn/go-colorable/colorable_appengine.go create mode 100644 vendor/github.com/mattn/go-colorable/colorable_others.go create mode 100644 vendor/github.com/mattn/go-colorable/colorable_windows.go create mode 100644 vendor/github.com/mattn/go-colorable/go.mod create mode 100644 vendor/github.com/mattn/go-colorable/go.sum create mode 100644 vendor/github.com/mattn/go-colorable/noncolorable.go create mode 100644 vendor/github.com/mattn/go-isatty/.travis.yml create mode 100644 vendor/github.com/mattn/go-isatty/LICENSE create mode 100644 vendor/github.com/mattn/go-isatty/README.md create mode 100644 vendor/github.com/mattn/go-isatty/doc.go create mode 100644 vendor/github.com/mattn/go-isatty/go.mod create mode 100644 vendor/github.com/mattn/go-isatty/go.sum create mode 100644 vendor/github.com/mattn/go-isatty/isatty_android.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_bsd.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_others.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_solaris.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_tcgets.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_windows.go diff --git a/docs/cmd/kn_service.md b/docs/cmd/kn_service.md index 5b4a36f95c..5a336fb0ca 100644 --- a/docs/cmd/kn_service.md +++ b/docs/cmd/kn_service.md @@ -28,7 +28,7 @@ kn service [flags] * [kn](kn.md) - Knative client * [kn service create](kn_service_create.md) - Create a service. * [kn service delete](kn_service_delete.md) - Delete a service. -* [kn service describe](kn_service_describe.md) - Describe available services. +* [kn service describe](kn_service_describe.md) - Show details for a given service * [kn service list](kn_service_list.md) - List available services. * [kn service update](kn_service_update.md) - Update a service. diff --git a/docs/cmd/kn_service_describe.md b/docs/cmd/kn_service_describe.md index 6c77c5f918..2b0e3eff19 100644 --- a/docs/cmd/kn_service_describe.md +++ b/docs/cmd/kn_service_describe.md @@ -1,10 +1,10 @@ ## kn service describe -Describe available services. +Show details for a given service ### Synopsis -Describe available services. +Show details for a given service ``` kn service describe NAME [flags] @@ -13,11 +13,11 @@ kn service describe NAME [flags] ### Options ``` - --allow-missing-template-keys If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. (default true) - -h, --help help for describe - -n, --namespace string List the requested object(s) in given namespace. - -o, --output string Output format. One of: json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-file. (default "yaml") - --template string Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]. + -a, --all don't truncate long information + -c, --color use colorful output + -h, --help help for describe + --hipster 🤓 + -n, --namespace string List the requested object(s) in given namespace. ``` ### Options inherited from parent commands diff --git a/go.mod b/go.mod index 46743be934..575bc5c087 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/knative/client require ( github.com/cpuguy83/go-md2man v1.0.10 // indirect github.com/evanphx/json-patch v4.5.0+incompatible // indirect + github.com/fatih/color v1.7.0 github.com/ghodss/yaml v1.0.0 // indirect github.com/gogo/protobuf v1.2.1 // indirect github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect @@ -22,6 +23,7 @@ require ( github.com/knative/serving v0.6.0 github.com/knative/test-infra v0.0.0-20190702025031-91d37e4abc30 github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a // indirect + github.com/mattn/go-colorable v0.1.2 // indirect github.com/mitchellh/go-homedir v1.1.0 github.com/modern-go/reflect2 v1.0.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect diff --git a/pkg/kn/commands/service/service_describe.go b/pkg/kn/commands/service/service_describe.go index 2e09f2e06e..3a27bc02c5 100644 --- a/pkg/kn/commands/service/service_describe.go +++ b/pkg/kn/commands/service/service_describe.go @@ -16,48 +16,508 @@ package service import ( "errors" + "fmt" + "io" + "regexp" + "sort" + "strconv" + "strings" + "time" + + "github.com/fatih/color" + "github.com/knative/client/pkg/printers" + "github.com/knative/client/pkg/serving" + "github.com/knative/pkg/apis" + "github.com/knative/pkg/apis/duck/v1beta1" + "github.com/knative/serving/pkg/apis/serving/v1alpha1" "github.com/knative/client/pkg/kn/commands" "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/duration" ) +// Command for printing out a description of a service, meant to be consumed by humans +// It will show information about the serivce itself, but also a summary +// about the associated revisions. +// "kn service describe" knows three modes, which can be combined: +// +// `--all` : Print more information. By default only are shorter summary is shown +// `--color` : Use a colorful output (but only when on a tty) +// `--hipster`: Experimental output mode for a very compact representation using emojis + +// Whether to print extended information +var printAll bool + +// Max length When to truncate long strings (when not "all" mode switched on) +var truncateAt = 100 + +// View object for collecting revision related information in the context +// of a Service +type revisionDesc struct { + name string + configuration string + configurationGeneration int + creationTimestamp time.Time + + percent int + latest *bool + + logUrl string + timeoutSeconds *int64 + + image string + imageDigest string + env []string + port *int32 +} + +// [REMOVE COMMENT WHEN MOVING TO 0.7.0] +// For transition to v1beta1 this command uses the migration approach as described +// in https://docs.google.com/presentation/d/1mOhnhy8kA4-K9Necct-NeIwysxze_FUule-8u5ZHmwA/edit#slide=id.p +// With serving 0.6.0 we are at step #1 +// I.e we first look at new fields of the v1alpha1 API before falling back to the original ones. +// As this command does not any updates, it's just a matter of fallbacks. +// [/REMOVE COMMENT WHEN MOVING TO 0.7.0] + +// Return a new command for describing a service. func NewServiceDescribeCommand(p *commands.KnParams) *cobra.Command { - serviceDescribePrintFlags := genericclioptions.NewPrintFlags("").WithDefaultOutput("yaml") - serviceDescribeCommand := &cobra.Command{ + command := &cobra.Command{ Use: "describe NAME", - Short: "Describe available services.", + Short: "Show details for a given service", RunE: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { - return errors.New("requires the service name.") + return errors.New("no service name provided") } - namespace, err := p.GetNamespace(cmd) + if len(args) > 1 { + return errors.New("more than one service name provided") + } + serviceName := args[0] + + namespace, err := GetNamespace(cmd) if err != nil { return err } - client, err := p.NewClient(namespace) if err != nil { return err } - describeService, err := client.GetService(args[0]) + printAll, err = cmd.Flags().GetBool("all") if err != nil { return err } - printer, err := serviceDescribePrintFlags.ToPrinter() + useColor, err := cmd.Flags().GetBool("color") if err != nil { return err } - err = printer.PrintObj(describeService, cmd.OutOrStdout()) + // Set color option globally + color.NoColor = !useColor + + service, err := client.Service(serviceName) if err != nil { return err } - return nil + // Additional revision related information + revisionDescs, err := getRevisionDescriptions(sClient, service, printAll) + + hipsterMode, err := cmd.Flags().GetBool("hipster") + if err != nil { + return err + } + if hipsterMode { + // Compact mode is completely separated as it used different way + // for formatting + return describeCompact(cmd.OutOrStdout(), service, revisionDescs) + } + return describe(cmd.OutOrStdout(), service, revisionDescs) }, } - commands.AddNamespaceFlags(serviceDescribeCommand.Flags(), false) - serviceDescribePrintFlags.AddFlags(serviceDescribeCommand) - return serviceDescribeCommand + flags := command.Flags() + AddNamespaceFlags(flags, false) + flags.BoolP("all", "a", false, "don't truncate long information") + flags.BoolP("color", "c", false, "use colorful output") + flags.Bool("hipster", false, "🤓") + return command +} + +// Main action describing the service +func describe(w io.Writer, service *v1alpha1.Service, revisions []*revisionDesc) error { + dw := printers.NewPrefixWriter(w) + + // Service info + writeService(dw, service) + dw.WriteLine() + if err := dw.Flush(); err != nil { + return err + } + + // Revisions summary info + writeRevisions(dw, revisions) + dw.WriteLine() + if err := dw.Flush(); err != nil { + return err + } + + // Condition info + writeConditions(dw, service) + if err := dw.Flush(); err != nil { + return err + } + + return nil +} + +// Write out main service information. Use colors for major items. +func writeService(dw printers.PrefixWriter, service *v1alpha1.Service) { + dw.WriteColsLn(printers.LEVEL_0, l("Name"), wc(service.Name, color.FgYellow)) + dw.WriteColsLn(printers.LEVEL_0, l("Namespace"), service.Namespace) + dw.WriteColsLn(printers.LEVEL_0, l("URL"), wc(extractURL(service), color.FgGreen)) + if service.Status.Address != nil { + dw.WriteColsLn(printers.LEVEL_0, l("Address"), service.Status.Address.Hostname) + } + writeMapDesc(dw, printers.LEVEL_0, service.Labels, l("Labels"), "") + writeMapDesc(dw, printers.LEVEL_0, service.Annotations, l("Annotations"), "") + dw.WriteColsLn(printers.LEVEL_0, l("Age"), age(service.CreationTimestamp.Time)) +} + +// Write out revisions associated with this service. By default only active +// target revisions are printed, but with --all also inactive revisions +// created by this services are shown +func writeRevisions(dw printers.PrefixWriter, revisions []*revisionDesc) { + dw.WriteColsLn(printers.LEVEL_0, l("Revisions")) + for _, revisionDesc := range revisions { + dw.WriteColsLn(printers.LEVEL_1, formatPercentage(revisionDesc.percent), l("Name"), getRevisionNameWithGenerationAndAge(revisionDesc)) + dw.WriteColsLn(printers.LEVEL_1, "", l("Image"), getImageDesc(revisionDesc)) + if revisionDesc.port != nil { + dw.WriteColsLn(printers.LEVEL_1, "", l("Port"), strconv.FormatInt(int64(*revisionDesc.port), 10)) + } + writeSliceDesc(dw, printers.LEVEL_1, revisionDesc.env, l("Env"), "\t") + } +} + +// Print out a table with conditions. Use green for 'ok', and red for 'nok' if color is enabled +func writeConditions(dw printers.PrefixWriter, service *v1alpha1.Service) { + dw.WriteColsLn(printers.LEVEL_0, l("Conditions")) + maxLen := getMaxTypeLen(service.Status.Conditions) + formatHeader := "%-2s %-" + strconv.Itoa(maxLen) + "s %6s %-s\n" + formatRow := "%-" + strconv.Itoa(2+colorOffset()) + "s %-" + strconv.Itoa(maxLen+colorOffset()) + "s %6s %-s\n" + dw.Write(printers.LEVEL_1, formatHeader, "OK", "TYPE", "AGE", "REASON") + for _, condition := range service.Status.Conditions { + ok := formatStatus(condition.Status) + reason := condition.Reason + if printAll && reason != "" { + reason = fmt.Sprintf("%s (%s)", reason, condition.Message) + } + dw.Write(printers.LEVEL_1, formatRow, ok, formatConditionType(condition), age(condition.LastTransitionTime.Inner.Time), reason) + } +} + +// ====================================================================================== +// Helper functions + +// Format label depending whether color mode is on or not +func l(label string) string { + return label + ":" +} + +// Get a colored string if color is enabled +func wc(value string, attributes ...color.Attribute) string { + return color.New(attributes...).Sprintf("%s", value) +} + +// How many invisible characters are used when coloring is switched on +func colorOffset() int { + return len(color.RedString("")) +} + +// ====================================================================================== + +// Format the revision name along with its generation. Use colors if enabled. +func getRevisionNameWithGenerationAndAge(desc *revisionDesc) string { + return wc(desc.name, color.FgYellow) + " " + + wc("["+strconv.Itoa(desc.configurationGeneration)+"]", color.FgHiBlack) + + " " + + wc("("+age(desc.creationTimestamp)+")", color.FgHiBlack) +} + +// Used for conditions table to do own formatting for the table, +// as the tabbed writer doesn't work nicely with colors +func getMaxTypeLen(conditions v1beta1.Conditions) int { + max := 0 + for _, condition := range conditions { + if len(condition.Type) > max { + max = len(condition.Type) + } + } + return max +} + +// Color the type of the conditions +func formatConditionType(condition apis.Condition) string { + switch condition.Status { + case v1.ConditionTrue: + return color.HiGreenString(string(condition.Type)) + case v1.ConditionFalse: + return color.HiRedString(string(condition.Type)) + default: + return string(condition.Type) + } +} + +// Status in ASCII format +func formatStatus(status corev1.ConditionStatus) string { + switch status { + case v1.ConditionTrue: + return color.HiGreenString("++") + case v1.ConditionFalse: + return color.HiRedString("--") + default: + return "" + } +} + +// Return either image name with tag or together with its resolved digest +func getImageDesc(desc *revisionDesc) string { + image := color.CyanString(desc.image) + if printAll && desc.imageDigest != "" { + digest := color.HiBlackString("(" + shortenDigest(desc.imageDigest) + ")") + return fmt.Sprintf("%s %s", image, digest) + } + return image +} + +// Extract pure sha sum and shorten to 8 digits, +// as the digest should to be user consumable. Use the resource via `kn service get` +// to get to the full sha +func shortenDigest(digest string) string { + digestRegexp := regexp.MustCompile(`(?i)sha256:([0-9a-f]+)`) + match := digestRegexp.FindStringSubmatch(digest) + if len(match) > 1 { + return string(match[1][:12]) + } + return digest +} + +// Write a map either compact in a single line (possibly truncated) or, if printAll is set, +// over multiple line, one line per key-value pair +func writeMapDesc(dw printers.PrefixWriter, indent int, m map[string]string, label string, labelPrefix string) { + if len(m) == 0 { + return + } + + if printAll { + l := labelPrefix + label + for key, value := range m { + dw.WriteColsLn(indent, l, key+"="+value) + l = labelPrefix + } + return + } + + dw.WriteColsLn(indent, label, joinAndTruncate(m)) +} + +// Writer a slice compact (printAll == false) in one line, or over multiple line +// with key-value line-by-line (printAll == true) +func writeSliceDesc(dw printers.PrefixWriter, indent int, s []string, label string, labelPrefix string) { + + if len(s) == 0 { + return + } + + if printAll { + l := labelPrefix + label + for _, value := range s { + dw.WriteColsLn(indent, l, value) + l = labelPrefix + } + return + } + + joined := strings.Join(s, ", ") + if len(joined) > truncateAt { + joined = joined[:truncateAt-4] + " ..." + } + dw.WriteColsLn(indent, labelPrefix+label, joined) +} + +// Join to key=value pair, comma separated, and truncate if longer than a limit +func joinAndTruncate(m map[string]string) string { + ret := "" + for key, value := range m { + ret += fmt.Sprintf("%s=%s, ", key, value) + if len(ret) > truncateAt { + break + } + } + // cut of two latest chars + ret = strings.TrimRight(ret, ", ") + if len(ret) <= truncateAt { + return ret + } + return string(ret[:truncateAt-4]) + " ..." +} + +// Format target percentage that it fits in the revision table +func formatPercentage(percentage int) string { + if percentage == 0 { + return " -" + } + return fmt.Sprintf("%-3d%%", percentage) +} + +func age(t time.Time) string { + if t.IsZero() { + return "" + } + return duration.ShortHumanDuration(time.Now().Sub(t)) +} + +// Call the backend to query revisions for the given service and build up +// the view objects used for output +func getRevisionDescriptions(client *serving.NamespacedClient, service *v1alpha1.Service, all bool) ([]*revisionDesc, error) { + revisionDescs := make(map[string]*revisionDesc) + + trafficTargets := service.Status.Traffic + + for _, target := range trafficTargets { + revision, err := extractRevisionFromTarget(client, target) + if err != nil { + return nil, fmt.Errorf("cannot extract revision from service %s: %v", service.Name, err) + } + revisionDescs[revision.Name], err = newRevisionDesc(revision, &target) + if err != nil { + return nil, err + } + } + if all { + if err := completeWithUntargetedRevisions(client, service, revisionDescs); err != nil { + return nil, err + } + } + return orderByConfigurationGeneration(revisionDescs), nil +} + +// Order the list of revisions so that the newest revisions are at the top +func orderByConfigurationGeneration(descs map[string]*revisionDesc) []*revisionDesc { + descsList := make([]*revisionDesc, len(descs)) + idx := 0 + for _, desc := range descs { + descsList[idx] = desc + idx++ + } + sort.SliceStable(descsList, func(i, j int) bool { + return descsList[i].configurationGeneration > descsList[j].configurationGeneration + }) + return descsList +} + +func completeWithUntargetedRevisions(client *serving.NamespacedClient, service *v1alpha1.Service, descs map[string]*revisionDesc) error { + revisions, err := client.RevisionsForService(service) + if err != nil { + return err + } + for _, revision := range revisions { + if _, ok := descs[revision.Name]; !ok { + descs[revision.Name], err = newRevisionDesc(revision, nil) + if err != nil { + return err + } + } + } + return nil +} + +func newRevisionDesc(revision *v1alpha1.Revision, target *v1alpha1.TrafficTarget) (*revisionDesc, error) { + container := extractContainer(revision) + generation, err := strconv.ParseInt(revision.Labels["serving.knative.dev/configurationGeneration"], 0, 0) + if err != nil { + return nil, fmt.Errorf("cannot extract configuration generation for revision %s: %v", revision.Name, err) + } + revisionDesc := revisionDesc{ + name: revision.Name, + logUrl: revision.Status.LogURL, + timeoutSeconds: revision.Spec.TimeoutSeconds, + imageDigest: revision.Status.ImageDigest, + creationTimestamp: revision.CreationTimestamp.Time, + + configurationGeneration: int(generation), + configuration: revision.Labels["serving.knative.dev/configuration"], + } + + addContainerInfo(&revisionDesc, container) + addTargetInfo(&revisionDesc, target) + return &revisionDesc, nil +} + +func addTargetInfo(desc *revisionDesc, target *v1alpha1.TrafficTarget) { + if target != nil { + desc.percent = target.Percent + desc.latest = target.LatestRevision + } +} + +func addContainerInfo(desc *revisionDesc, container *v1.Container) { + addImage(desc, container) + addEnv(desc, container) + addPort(desc, container) +} + +func addEnv(desc *revisionDesc, container *v1.Container) { + envVars := make([]string, 0, len(container.Env)) + for _, env := range container.Env { + var value string + if env.ValueFrom != nil { + value = "[ref]" + } else { + value = env.Value + } + envVars = append(envVars, fmt.Sprintf("%s=%s", env.Name, value)) + } + desc.env = envVars +} + +func addPort(desc *revisionDesc, container *v1.Container) { + if len(container.Ports) > 0 { + port := container.Ports[0].ContainerPort + desc.port = &port + } +} + +func addImage(desc *revisionDesc, container *v1.Container) { + desc.image = container.Image +} + +func extractContainer(revision *v1alpha1.Revision) *v1.Container { + if revision.Spec.Containers != nil && len(revision.Spec.Containers) > 0 { + return &revision.Spec.Containers[0] + } + return revision.Spec.DeprecatedContainer +} + +func extractRevisionFromTarget(client *serving.NamespacedClient, target v1alpha1.TrafficTarget) (*v1alpha1.Revision, error) { + var revisionName = target.RevisionName + if revisionName == "" { + configurationName := target.ConfigurationName + if configurationName == "" { + return nil, fmt.Errorf("neither RevisionName nor ConfigurationName set") + } + configuration, err := client.Configuration(configurationName) + if err != nil { + return nil, err + } + revisionName = configuration.Status.LatestCreatedRevisionName + } + return client.Revision(revisionName) +} + +func extractURL(service *v1alpha1.Service) string { + status := service.Status + if status.URL != nil { + return status.URL.String() + } + return status.DeprecatedDomain } diff --git a/pkg/kn/commands/service/service_describe_test.go b/pkg/kn/commands/service/service_describe_test.go index a80b139a49..3c8c9157d6 100644 --- a/pkg/kn/commands/service/service_describe_test.go +++ b/pkg/kn/commands/service/service_describe_test.go @@ -15,23 +15,35 @@ package service import ( + "bytes" + "regexp" + "strings" "encoding/json" "testing" + duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" "github.com/knative/client/pkg/kn/commands" "github.com/knative/serving/pkg/apis/serving/v1alpha1" + serving "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1" + "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake" "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - client_testing "k8s.io/client-go/testing" - "sigs.k8s.io/yaml" + clienttesting "k8s.io/client-go/testing" ) +func fakeServiceDescribe(args []string, response *v1alpha1.Service) (action clienttesting.Action, output string, err error) { + buf := new(bytes.Buffer) + fakeServing := &fake.FakeServingV1alpha1{Fake: &clienttesting.Fake{}} + cmd := NewKnCommand(KnParams{ + Output: buf, + ServingFactory: func() (serving.ServingV1alpha1Interface, error) { return fakeServing, nil }, + }) func fakeServiceDescribe(args []string, response *v1alpha1.Service) (action client_testing.Action, output string, err error) { knParams := &commands.KnParams{} cmd, fakeServing, buf := commands.CreateTestKnCommand(NewServiceCommand(knParams), knParams) fakeServing.AddReactor("*", "*", - func(a client_testing.Action) (bool, runtime.Object, error) { + func(a clienttesting.Action) (bool, runtime.Object, error) { action = a return true, response, nil }) @@ -46,9 +58,11 @@ func fakeServiceDescribe(args []string, response *v1alpha1.Service) (action clie func TestEmptyServiceDescribe(t *testing.T) { _, _, err := fakeServiceDescribe([]string{"service", "describe"}, &v1alpha1.Service{}) - expectedError := "requires the service name." - if err == nil || err.Error() != expectedError { - t.Fatal("expect to fail with missing service name") + if err == nil || + !strings.Contains(err.Error(), "no") || + !strings.Contains(err.Error(), "service") || + !strings.Contains(err.Error(), "provided") { + t.Fatalf("expect to fail with missing service name (got: %v)", err) } } @@ -62,6 +76,12 @@ func TestServiceDescribeDefaultOutput(t *testing.T) { Name: "foo", Namespace: "default", }, + Status: v1alpha1.ServiceStatus{ + RouteStatusFields: v1alpha1.RouteStatusFields{ + DeprecatedDomain: "foo.default.example.com", + Address: &duckv1alpha1.Addressable{Hostname: "foo.default.svc.cluster.local"}, + }, + }, } action, output, err := fakeServiceDescribe([]string{"service", "describe", "test-foo"}, &expectedService) if err != nil { @@ -73,17 +93,19 @@ func TestServiceDescribeDefaultOutput(t *testing.T) { t.Fatalf("Bad action %v", action) } - jsonData, err := yaml.YAMLToJSON([]byte(output)) - if err != nil { - t.Fatal(err) - } - var returnedService v1alpha1.Service - err = json.Unmarshal(jsonData, &returnedService) + assertMatches(t, output, "Name:\\s+foo") + assertMatches(t, output, "Namespace:\\s+default") + assertMatches(t, output, "Address:\\s+foo.default.svc.cluster.local") + assertMatches(t, output, "URL:\\s+foo.default.example.com") + assertMatches(t, output, "Age:") +} + +func assertMatches(t *testing.T, value, expr string) { + ok, err := regexp.MatchString(expr, value) if err != nil { - t.Fatal(err) + t.Fatalf("invalid pattern %q. %v", expr, err) } - - if !equality.Semantic.DeepEqual(expectedService, returnedService) { - t.Fatal("mismatched objects") + if !ok { + t.Errorf("got %s which does not match %s\n", value, expr) } } diff --git a/pkg/kn/commands/service_describe_compact.go b/pkg/kn/commands/service_describe_compact.go new file mode 100644 index 0000000000..c5538f8ea4 --- /dev/null +++ b/pkg/kn/commands/service_describe_compact.go @@ -0,0 +1,180 @@ +package commands + +import ( + "fmt" + "github.com/fatih/color" + "github.com/knative/client/pkg/printers" + "github.com/knative/serving/pkg/apis/serving/v1alpha1" + "io" + v1 "k8s.io/api/core/v1" + "strconv" + "strings" +) + +var emojiMap = map[string]string{ + "Name": "🆔", + "Namespace": "📁", + "URL": "🌍", + "Address": "🏠", + "Annotations": "✏️", + "Labels": "🏷️", + "Age": "⏰", + "Revisions": "❄️", + "Image": "📦", + "Env": "🌵", + "Port": "🔌", + "Generation": "📶", +} + +func describeCompact(w io.Writer, service *v1alpha1.Service, revisions []*revisionDesc) error { + dw := printers.NewPrefixWriter(w) + + writeServiceCompact(dw, service) + dw.WriteLine(separator()) + if err := dw.Flush(); err != nil { + return err + } + + writeRevisionsCompact(dw, revisions) + dw.WriteLine(separator()) + if err := dw.Flush(); err != nil { + return err + } + + writeConditionsCompact(dw, service) + dw.WriteLine(separator()) + if err := dw.Flush(); err != nil { + return err + } + + return nil +} + +func separator() string { + return strings.Repeat("─", 80) +} + +// Emojii variation of label +func e(label string) string { + return emojiMap[label] +} + +// Write out service compactly. We can't use a tabbed writer here as it can't be used runes (emojis) +func writeServiceCompact(dw printers.PrefixWriter, service *v1alpha1.Service) { + dw.WriteLine(separator()) + dw.Write(printers.LEVEL_0, "%s %s │ %s %s │ %s %s\n", + e("Name"), service.Name, e("Namespace"), + service.Namespace, e("Age"), age(service.CreationTimestamp.Time)) + dw.WriteLine(separator()) + dw.Write(printers.LEVEL_0, "%s %s\n", e("URL"), extractURL(service)) + if service.Status.Address != nil { + dw.Write(printers.LEVEL_0, "%s %s\n", e("Address"), service.Status.Address.Hostname) + } + writeMapDescCompact(dw, printers.LEVEL_0, service.Labels, e("Labels")) + writeMapDescCompact(dw, printers.LEVEL_0, service.Annotations, e("Annotations")) +} + +func writeRevisionsCompact(dw printers.PrefixWriter, revisions []*revisionDesc) { + firstLabel := e("Revisions") + for _, desc := range revisions { + dw.Write(printers.LEVEL_0, "%s %4s %s %s\n", firstLabel, getPercentageCompact(desc), e("Name"), getRevisionNameWithGenerationAndAge(desc)) + dw.Write(printers.LEVEL_0, " %s %s\n", e("Image"), getImageDesc(desc)) + if desc.port != nil { + dw.WriteColsLn(printers.LEVEL_0, " %s %s\n", e("Port"), strconv.FormatInt(int64(*desc.port), 10)) + } + writeEnvCompact(dw, desc.env) + firstLabel = " " + } +} + +func writeConditionsCompact(dw printers.PrefixWriter, service *v1alpha1.Service) { + maxLen := getMaxTypeLen(service.Status.Conditions) + formatRow := "%s %s %-" + strconv.Itoa(maxLen+colorOffset()) + "s %6s %-s\n" + firstLabel := extractConditionsEmoji(service) + for _, condition := range service.Status.Conditions { + status := formatConditionStatusEmoji(condition.Status) + conditionType := formatConditionType(condition) + reason := condition.Reason + age := wc(age(condition.LastTransitionTime.Inner.Time), color.FgHiBlack) + if printAll && reason != "" { + reason = fmt.Sprintf("%s (%s)", reason, condition.Message) + } + dw.Write(printers.LEVEL_0, formatRow, firstLabel, status, conditionType, age, reason) + firstLabel = " " + } +} + +func formatConditionStatusEmoji(status v1.ConditionStatus) string { + switch status { + case v1.ConditionTrue: + return "👍" + case v1.ConditionFalse: + return "👎" + } + return "🤔" + +} + +func extractConditionsEmoji(service *v1alpha1.Service) string { + ret := "☀️" + for _, condition := range service.Status.Conditions { + if condition.Status == v1.ConditionFalse { + return "🌧️" + } + if condition.Status == v1.ConditionUnknown { + ret = "☁️" + } + } + return ret +} + +func getPercentageCompact(desc *revisionDesc) string { + percent := desc.percent + if percent == 100 { + return "💯 " + } + if percent == 0 { + return "💤 " + } + return fmt.Sprintf("%4d%%", desc.percent) +} + +// ================================== + +func writeMapDescCompact(dw printers.PrefixWriter, indent int, data map[string]string, label string) { + if len(data) == 0 { + return + } + + if printAll { + prefix := label + for key, value := range data { + dw.Write(indent, "%s %s=%s\n", prefix, key, value) + prefix = " " + } + return + } + + dw.Write(indent, "%s %s\n", label, joinAndTruncate(data)) +} + +func writeEnvCompact(dw printers.PrefixWriter, env []string) { + if len(env) == 0 { + return + } + label := e("Env") + if printAll { + prefix := label + for _, value := range env { + dw.Write(printers.LEVEL_0, " %s %s\n", prefix, value) + prefix = " " + } + return + } + + joined := strings.Join(env, ", ") + if len(joined) > truncateAt { + joined = joined[:truncateAt-4] + " ..." + } + dw.Write(printers.LEVEL_0, " %s %s\n", label, joined) +} diff --git a/pkg/printers/prefixwriter.go b/pkg/printers/prefixwriter.go new file mode 100644 index 0000000000..157bbdedfd --- /dev/null +++ b/pkg/printers/prefixwriter.go @@ -0,0 +1,86 @@ +package printers + +import ( + "fmt" + "io" + "strings" + "text/tabwriter" +) + +type flusher interface { + Flush() error +} + +// NewPrefixWriter creates a new PrefixWriter. +func NewPrefixWriter(out io.Writer) PrefixWriter { + tabWriter := tabwriter.NewWriter(out, 0, 8, 2, ' ', 0) + return &prefixWriter{out: tabWriter} +} + +// PrefixWriter can write text at various indentation levels. +type PrefixWriter interface { + // Write writes text with the specified indentation level. + Write(level int, format string, a ...interface{}) + // WriteLine writes an entire line with no indentation level. + WriteLine(a ...interface{}) + // Write columns with an initial indentation + WriteCols(level int, cols ...string) + // Write columns with an initial indentation and a newline at the end + WriteColsLn(level int, cols ...string) + // Flush forces indentation to be reset. + Flush() error +} + +// prefixWriter implements PrefixWriter +type prefixWriter struct { + out io.Writer +} + +var _ PrefixWriter = &prefixWriter{} + +// Each level has 2 spaces for PrefixWriter +const ( + LEVEL_0 = iota + LEVEL_1 + LEVEL_2 + LEVEL_3 +) + +func (pw *prefixWriter) Write(level int, format string, a ...interface{}) { + levelSpace := " " + prefix := "" + for i := 0; i < level; i++ { + prefix += levelSpace + } + fmt.Fprintf(pw.out, prefix+format, a...) +} + +func (pw *prefixWriter) WriteCols(level int, cols ...string) { + ss := make([]string, len(cols)) + for i, _ := range cols { + ss[i] = "%s" + } + format := strings.Join(ss, "\t") + s := make([]interface{}, len(cols)) + for i, v := range cols { + s[i] = v + } + + pw.Write(level, format, s...) +} + +func (pw *prefixWriter) WriteColsLn(level int, cols ...string) { + pw.WriteCols(level, cols...) + pw.WriteLine() +} + +func (pw *prefixWriter) WriteLine(a ...interface{}) { + fmt.Fprintln(pw.out, a...) +} + +func (pw *prefixWriter) Flush() error { + if f, ok := pw.out.(flusher); ok { + return f.Flush() + } + return fmt.Errorf("outpustream %v doesn't support Flush", pw.out) +} diff --git a/pkg/printers/tableprinter.go b/pkg/printers/tableprinter.go index ff49ed52b7..0f3c18d6cc 100644 --- a/pkg/printers/tableprinter.go +++ b/pkg/printers/tableprinter.go @@ -43,7 +43,7 @@ func NewTablePrinter(options PrintOptions) *HumanReadablePrinter { func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error { w, found := output.(*tabwriter.Writer) if !found { - w = GetNewTabWriter(output) + w = NewTabWriter(output) output = w defer w.Flush() } diff --git a/pkg/printers/tabwriter.go b/pkg/printers/tabwriter.go index 717e5e6e1b..c29efe4837 100644 --- a/pkg/printers/tabwriter.go +++ b/pkg/printers/tabwriter.go @@ -30,7 +30,7 @@ const ( tabwriterFlags = tabwriter.TabIndent ) -// GetNewTabWriter returns a tabwriter that translates tabbed columns in input into properly aligned text. -func GetNewTabWriter(output io.Writer) *tabwriter.Writer { +// NewTabWriter returns a tabwriter that translates tabbed columns in input into properly aligned text. +func NewTabWriter(output io.Writer) *tabwriter.Writer { return tabwriter.NewWriter(output, tabwriterMinWidth, tabwriterWidth, tabwriterPadding, tabwriterPadChar, tabwriterFlags) } diff --git a/pkg/serving/client.go b/pkg/serving/client.go new file mode 100644 index 0000000000..40320e828a --- /dev/null +++ b/pkg/serving/client.go @@ -0,0 +1,78 @@ +package serving + +import ( + "fmt" + "github.com/knative/serving/pkg/apis/serving/v1alpha1" + client_v1alpha1 "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type NamespacedClient struct { + client client_v1alpha1.ServingV1alpha1Interface + namespace string +} + +func NewNamespacedClient(client client_v1alpha1.ServingV1alpha1Interface, namespace string) *NamespacedClient { + return &NamespacedClient{ + client: client, + namespace: namespace, + } +} + +func (cl *NamespacedClient) Service(name string) (*v1alpha1.Service, error) { + service, err := cl.client.Services(cl.namespace).Get(name, v1.GetOptions{}) + if err != nil { + return nil, err + } + service.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{ + Group: "knative.dev", + Version: "v1alpha1", + Kind: "Service"}) + return service, nil +} + +func (cl *NamespacedClient) Revision(name string) (*v1alpha1.Revision, error) { + revision, err := cl.client.Revisions(cl.namespace).Get(name, v1.GetOptions{}) + if err != nil { + return nil, err + } + updateRevisionWithGVK(revision) + return revision, nil +} + +func (cl *NamespacedClient) Configuration(name string) (*v1alpha1.Configuration, error) { + cofiguration, err := cl.client.Configurations(cl.namespace).Get(name, v1.GetOptions{}) + if err != nil { + return nil, err + } + cofiguration.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{ + Group: "knative.dev", + Version: "v1alpha1", + Kind: "Configuration"}) + return cofiguration, nil +} + +func (cl *NamespacedClient) RevisionsForService(service *v1alpha1.Service) ([]*v1alpha1.Revision, error) { + labelSelector := fmt.Sprintf("serving.knative.dev/service=%s", service.Name) + revisions, err := cl.client.Revisions(cl.namespace).List(v1.ListOptions{LabelSelector: labelSelector}) + if err != nil { + return nil, err + } + revisionSlice := make([]*v1alpha1.Revision, len(revisions.Items)) + for idx, _ := range revisions.Items { + revision := revisions.Items[idx].DeepCopy() + updateRevisionWithGVK(revision) + revisionSlice[idx] = revision + } + return revisionSlice, nil +} + +// ============================================================================ + +func updateRevisionWithGVK(revision *v1alpha1.Revision) { + revision.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{ + Group: "knative.dev", + Version: "v1alpha1", + Kind: "Revision"}) +} diff --git a/vendor/github.com/fatih/color/.travis.yml b/vendor/github.com/fatih/color/.travis.yml new file mode 100644 index 0000000000..95f8a1ff5c --- /dev/null +++ b/vendor/github.com/fatih/color/.travis.yml @@ -0,0 +1,5 @@ +language: go +go: + - 1.8.x + - tip + diff --git a/vendor/github.com/fatih/color/Gopkg.lock b/vendor/github.com/fatih/color/Gopkg.lock new file mode 100644 index 0000000000..7d879e9caf --- /dev/null +++ b/vendor/github.com/fatih/color/Gopkg.lock @@ -0,0 +1,27 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/mattn/go-colorable" + packages = ["."] + revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" + version = "v0.0.9" + +[[projects]] + name = "github.com/mattn/go-isatty" + packages = ["."] + revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" + version = "v0.0.3" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = ["unix"] + revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "e8a50671c3cb93ea935bf210b1cd20702876b9d9226129be581ef646d1565cdc" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/fatih/color/Gopkg.toml b/vendor/github.com/fatih/color/Gopkg.toml new file mode 100644 index 0000000000..ff1617f71d --- /dev/null +++ b/vendor/github.com/fatih/color/Gopkg.toml @@ -0,0 +1,30 @@ + +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" + + +[[constraint]] + name = "github.com/mattn/go-colorable" + version = "0.0.9" + +[[constraint]] + name = "github.com/mattn/go-isatty" + version = "0.0.3" diff --git a/vendor/github.com/fatih/color/LICENSE.md b/vendor/github.com/fatih/color/LICENSE.md new file mode 100644 index 0000000000..25fdaf639d --- /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 0000000000..3fc9544602 --- /dev/null +++ b/vendor/github.com/fatih/color/README.md @@ -0,0 +1,179 @@ +# Color [![GoDoc](https://godoc.org/github.com/fatih/color?status.svg)](https://godoc.org/github.com/fatih/color) [![Build Status](https://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](https://i.imgur.com/c1JI0lA.png) + + +## Install + +```bash +go get github.com/fatih/color +``` + +Note that the `vendor` folder is here for stability. Remove the folder if you +already have the dependencies in your GOPATH. + +## 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.") +``` + +### Use your own output (io.Writer) + +```go +// Use your own io.Writer output +color.New(color.FgBlue).Fprintln(myWriter, "blue color!") + +blue := color.New(color.FgBlue) +blue.Fprint(writer, "This will print text in blue.") +``` + +### 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...") +``` + +### Custom fprint functions (FprintFunc) + +```go +blue := color.New(FgBlue).FprintfFunc() +blue(myWriter, "important notice: %s", stars) + +// Mix up with multiple attributes +success := color.New(color.Bold, color.FgGreen).FprintlnFunc() +success(myWriter, "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.Println("This", color.RedString("warning"), "should be not neglected.") +fmt.Printf("%v %v\n", 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/Enable color + +There might be a case where you want to explicitly disable/enable color output. the +`go-isatty` package will automatically disable color output for non-tty output streams +(for example if the output were piped directly to `less`) + +`Color` has support to disable/enable colors both globally and for single color +definitions. 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 0000000000..91c8e9f062 --- /dev/null +++ b/vendor/github.com/fatih/color/color.go @@ -0,0 +1,603 @@ +package color + +import ( + "fmt" + "io" + "os" + "strconv" + "strings" + "sync" + + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" +) + +var ( + // 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. + NoColor = os.Getenv("TERM") == "dumb" || + (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) + + // Output defines the standard output of the print functions. By default + // os.Stdout is used. + Output = colorable.NewColorableStdout() + + // Error defines a color supporting writer for os.Stderr. + Error = colorable.NewColorableStderr() + + // colorsCache is used to reduce the count of created Color objects and + // allows to reuse already created objects with required Attribute. + colorsCache = make(map[Attribute]*Color) + colorsCacheMu sync.Mutex // protects colorsCache +) + +// 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() +} + +func (c *Color) setWriter(w io.Writer) *Color { + if c.isNoColorSet() { + return c + } + + fmt.Fprintf(w, c.format()) + return c +} + +func (c *Color) unsetWriter(w io.Writer) { + if c.isNoColorSet() { + return + } + + if NoColor { + return + } + + fmt.Fprintf(w, "%s[%dm", escape, Reset) +} + +// 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 +} + +// Fprint formats using the default formats for its operands and writes to w. +// Spaces are added between operands when neither is a string. +// It returns the number of bytes written and any write error encountered. +// On Windows, users should wrap w with colorable.NewColorable() if w is of +// type *os.File. +func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) { + c.setWriter(w) + defer c.unsetWriter(w) + + return fmt.Fprint(w, a...) +} + +// 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...) +} + +// Fprintf formats according to a format specifier and writes to w. +// It returns the number of bytes written and any write error encountered. +// On Windows, users should wrap w with colorable.NewColorable() if w is of +// type *os.File. +func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { + c.setWriter(w) + defer c.unsetWriter(w) + + return fmt.Fprintf(w, format, 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...) +} + +// Fprintln formats using the default formats for its operands and writes to w. +// Spaces are always added between operands and a newline is appended. +// On Windows, users should wrap w with colorable.NewColorable() if w is of +// type *os.File. +func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { + c.setWriter(w) + defer c.unsetWriter(w) + + return fmt.Fprintln(w, 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...) +} + +// Sprint is just like Print, but returns a string instead of printing it. +func (c *Color) Sprint(a ...interface{}) string { + return c.wrap(fmt.Sprint(a...)) +} + +// Sprintln is just like Println, but returns a string instead of printing it. +func (c *Color) Sprintln(a ...interface{}) string { + return c.wrap(fmt.Sprintln(a...)) +} + +// Sprintf is just like Printf, but returns a string instead of printing it. +func (c *Color) Sprintf(format string, a ...interface{}) string { + return c.wrap(fmt.Sprintf(format, a...)) +} + +// FprintFunc returns a new function that prints the passed arguments as +// colorized with color.Fprint(). +func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) { + return func(w io.Writer, a ...interface{}) { + c.Fprint(w, 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...) + } +} + +// FprintfFunc returns a new function that prints the passed arguments as +// colorized with color.Fprintf(). +func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) { + return func(w io.Writer, format string, a ...interface{}) { + c.Fprintf(w, format, 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...) + } +} + +// FprintlnFunc returns a new function that prints the passed arguments as +// colorized with color.Fprintln(). +func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) { + return func(w io.Writer, a ...interface{}) { + c.Fprintln(w, 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 conjunction 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 conjunction 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 conjunction 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 formatted 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 conjunction 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 +} + +func getCachedColor(p Attribute) *Color { + colorsCacheMu.Lock() + defer colorsCacheMu.Unlock() + + c, ok := colorsCache[p] + if !ok { + c = New(p) + colorsCache[p] = c + } + + return c +} + +func colorPrint(format string, p Attribute, a ...interface{}) { + c := getCachedColor(p) + + if !strings.HasSuffix(format, "\n") { + format += "\n" + } + + if len(a) == 0 { + c.Print(format) + } else { + c.Printf(format, a...) + } +} + +func colorString(format string, p Attribute, a ...interface{}) string { + c := getCachedColor(p) + + if len(a) == 0 { + return c.SprintFunc()(format) + } + + return c.SprintfFunc()(format, a...) +} + +// Black is a convenient helper function to print with black foreground. A +// newline is appended to format by default. +func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) } + +// Red is a convenient helper function to print with red foreground. A +// newline is appended to format by default. +func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) } + +// Green is a convenient helper function to print with green foreground. A +// newline is appended to format by default. +func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) } + +// Yellow is a convenient helper function to print with yellow foreground. +// A newline is appended to format by default. +func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) } + +// Blue is a convenient helper function to print with blue foreground. A +// newline is appended to format by default. +func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) } + +// Magenta is a convenient helper function to print with magenta foreground. +// A newline is appended to format by default. +func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) } + +// Cyan is a convenient helper function to print with cyan foreground. A +// newline is appended to format by default. +func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) } + +// White is a convenient helper function to print with white foreground. A +// newline is appended to format by default. +func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) } + +// BlackString is a convenient helper function to return a string with black +// foreground. +func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) } + +// RedString is a convenient helper function to return a string with red +// foreground. +func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) } + +// GreenString is a convenient helper function to return a string with green +// foreground. +func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) } + +// YellowString is a convenient helper function to return a string with yellow +// foreground. +func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) } + +// BlueString is a convenient helper function to return a string with blue +// foreground. +func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) } + +// MagentaString is a convenient helper function to return a string with magenta +// foreground. +func MagentaString(format string, a ...interface{}) string { + return colorString(format, FgMagenta, a...) +} + +// CyanString is a convenient helper function to return a string with cyan +// foreground. +func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) } + +// WhiteString is a convenient helper function to return a string with white +// foreground. +func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) } + +// HiBlack is a convenient helper function to print with hi-intensity black foreground. A +// newline is appended to format by default. +func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) } + +// HiRed is a convenient helper function to print with hi-intensity red foreground. A +// newline is appended to format by default. +func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) } + +// HiGreen is a convenient helper function to print with hi-intensity green foreground. A +// newline is appended to format by default. +func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) } + +// HiYellow is a convenient helper function to print with hi-intensity yellow foreground. +// A newline is appended to format by default. +func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) } + +// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A +// newline is appended to format by default. +func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) } + +// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground. +// A newline is appended to format by default. +func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) } + +// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A +// newline is appended to format by default. +func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) } + +// HiWhite is a convenient helper function to print with hi-intensity white foreground. A +// newline is appended to format by default. +func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) } + +// HiBlackString is a convenient helper function to return a string with hi-intensity black +// foreground. +func HiBlackString(format string, a ...interface{}) string { + return colorString(format, FgHiBlack, a...) +} + +// HiRedString is a convenient helper function to return a string with hi-intensity red +// foreground. +func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) } + +// HiGreenString is a convenient helper function to return a string with hi-intensity green +// foreground. +func HiGreenString(format string, a ...interface{}) string { + return colorString(format, FgHiGreen, a...) +} + +// HiYellowString is a convenient helper function to return a string with hi-intensity yellow +// foreground. +func HiYellowString(format string, a ...interface{}) string { + return colorString(format, FgHiYellow, a...) +} + +// HiBlueString is a convenient helper function to return a string with hi-intensity blue +// foreground. +func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) } + +// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta +// foreground. +func HiMagentaString(format string, a ...interface{}) string { + return colorString(format, FgHiMagenta, a...) +} + +// HiCyanString is a convenient helper function to return a string with hi-intensity cyan +// foreground. +func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) } + +// HiWhiteString is a convenient helper function to return a string with hi-intensity white +// foreground. +func HiWhiteString(format string, a ...interface{}) string { + return colorString(format, FgHiWhite, a...) +} diff --git a/vendor/github.com/fatih/color/doc.go b/vendor/github.com/fatih/color/doc.go new file mode 100644 index 0000000000..cf1e96500f --- /dev/null +++ b/vendor/github.com/fatih/color/doc.go @@ -0,0 +1,133 @@ +/* +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 ..") + + // Hi-intensity colors + color.HiGreen("Bright green color.") + color.HiBlack("Bright black means gray..") + color.HiWhite("Shiny white color!") + +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.") + + // Use your own io.Writer output + color.New(color.FgBlue).Fprintln(myWriter, "blue color!") + + blue := color.New(color.FgBlue) + blue.Fprint(myWriter, "This will print text in blue.") + +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...") + +You can also FprintXxx functions to pass your own io.Writer: + + blue := color.New(FgBlue).FprintfFunc() + blue(myWriter, "important notice: %s", stars) + + // Mix up with multiple attributes + success := color.New(color.Bold, color.FgGreen).FprintlnFunc() + success(myWriter, 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 work 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/.travis.yml b/vendor/github.com/mattn/go-colorable/.travis.yml new file mode 100644 index 0000000000..98db8f060b --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - tip + +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -repotoken xnXqRGwgW3SXIguzxf90ZSK1GPYZPaGrw diff --git a/vendor/github.com/mattn/go-colorable/LICENSE b/vendor/github.com/mattn/go-colorable/LICENSE new file mode 100644 index 0000000000..91b5cef30e --- /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 0000000000..56729a92ca --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/README.md @@ -0,0 +1,48 @@ +# go-colorable + +[![Godoc Reference](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable) +[![Build Status](https://travis-ci.org/mattn/go-colorable.svg?branch=master)](https://travis-ci.org/mattn/go-colorable) +[![Coverage Status](https://coveralls.io/repos/github/mattn/go-colorable/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-colorable?branch=master) +[![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/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_appengine.go b/vendor/github.com/mattn/go-colorable/colorable_appengine.go new file mode 100644 index 0000000000..1f28d773d7 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/colorable_appengine.go @@ -0,0 +1,29 @@ +// +build appengine + +package colorable + +import ( + "io" + "os" + + _ "github.com/mattn/go-isatty" +) + +// NewColorable return new instance of Writer which handle escape sequence. +func NewColorable(file *os.File) io.Writer { + if file == nil { + panic("nil passed instead of *os.File to NewColorable()") + } + + return file +} + +// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +func NewColorableStdout() io.Writer { + return os.Stdout +} + +// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +func NewColorableStderr() io.Writer { + return os.Stderr +} 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 0000000000..887f203dc7 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/colorable_others.go @@ -0,0 +1,30 @@ +// +build !windows +// +build !appengine + +package colorable + +import ( + "io" + "os" + + _ "github.com/mattn/go-isatty" +) + +// NewColorable return new instance of Writer which handle escape sequence. +func NewColorable(file *os.File) io.Writer { + if file == nil { + panic("nil passed instead of *os.File to NewColorable()") + } + + return file +} + +// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +func NewColorableStdout() io.Writer { + return os.Stdout +} + +// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +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 0000000000..404e10ca02 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/colorable_windows.go @@ -0,0 +1,980 @@ +// +build windows +// +build !appengine + +package colorable + +import ( + "bytes" + "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) +) + +const ( + genericRead = 0x80000000 + genericWrite = 0x40000000 +) + +const ( + consoleTextmodeBuffer = 0x1 +) + +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") + procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW") + procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer") +) + +// Writer provide colorable Writer to the console +type Writer struct { + out io.Writer + handle syscall.Handle + althandle syscall.Handle + oldattr word + oldpos coord + rest bytes.Buffer +} + +// NewColorable return new instance of Writer which handle escape sequence from File. +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, oldpos: coord{0, 0}} + } + return file +} + +// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +func NewColorableStdout() io.Writer { + return NewColorable(os.Stdout) +} + +// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +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, +} + +// `\033]0;TITLESTR\007` +func doTitleSequence(er *bytes.Reader) error { + var c byte + var err error + + c, err = er.ReadByte() + if err != nil { + return err + } + if c != '0' && c != '2' { + return nil + } + c, err = er.ReadByte() + if err != nil { + return err + } + if c != ';' { + return nil + } + title := make([]byte, 0, 80) + for { + c, err = er.ReadByte() + if err != nil { + return err + } + if c == 0x07 || c == '\n' { + break + } + title = append(title, c) + } + if len(title) > 0 { + title8, err := syscall.UTF16PtrFromString(string(title)) + if err == nil { + procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8))) + } + } + return nil +} + +// Write write data on console +func (w *Writer) Write(data []byte) (n int, err error) { + var csbi consoleScreenBufferInfo + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + + handle := w.handle + + var er *bytes.Reader + if w.rest.Len() > 0 { + var rest bytes.Buffer + w.rest.WriteTo(&rest) + w.rest.Reset() + rest.Write(data) + er = bytes.NewReader(rest.Bytes()) + } else { + er = bytes.NewReader(data) + } + var bw [1]byte +loop: + for { + c1, err := er.ReadByte() + if err != nil { + break loop + } + if c1 != 0x1b { + bw[0] = c1 + w.out.Write(bw[:]) + continue + } + c2, err := er.ReadByte() + if err != nil { + break loop + } + + switch c2 { + case '>': + continue + case ']': + w.rest.WriteByte(c1) + w.rest.WriteByte(c2) + er.WriteTo(&w.rest) + if bytes.IndexByte(w.rest.Bytes(), 0x07) == -1 { + break loop + } + er = bytes.NewReader(w.rest.Bytes()[2:]) + err := doTitleSequence(er) + if err != nil { + break loop + } + w.rest.Reset() + continue + // https://github.com/mattn/go-colorable/issues/27 + case '7': + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + w.oldpos = csbi.cursorPosition + continue + case '8': + procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos))) + continue + case 0x5b: + // execute part after switch + default: + continue + } + + w.rest.WriteByte(c1) + w.rest.WriteByte(c2) + er.WriteTo(&w.rest) + + var buf bytes.Buffer + var m byte + for i, c := range w.rest.Bytes()[2:] { + if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { + m = c + er = bytes.NewReader(w.rest.Bytes()[2+i+1:]) + w.rest.Reset() + break + } + buf.Write([]byte(string(c))) + } + if m == 0 { + break loop + } + + switch m { + case 'A': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.y -= short(n) + procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'B': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.y += short(n) + procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'C': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x += short(n) + procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'D': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x -= short(n) + if csbi.cursorPosition.x < 0 { + csbi.cursorPosition.x = 0 + } + procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'E': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = 0 + csbi.cursorPosition.y += short(n) + procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'F': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = 0 + csbi.cursorPosition.y -= short(n) + procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'G': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = short(n - 1) + procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'H', 'f': + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + if buf.Len() > 0 { + token := strings.Split(buf.String(), ";") + switch len(token) { + case 1: + n1, err := strconv.Atoi(token[0]) + if err != nil { + continue + } + csbi.cursorPosition.y = short(n1 - 1) + case 2: + 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) + } + } else { + csbi.cursorPosition.y = 0 + } + procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'J': + n := 0 + if buf.Len() > 0 { + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + } + var count, written dword + var cursor coord + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + switch n { + case 0: + cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} + count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x) + case 1: + cursor = coord{x: csbi.window.left, y: csbi.window.top} + count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.window.top-csbi.cursorPosition.y)*dword(csbi.size.x) + case 2: + cursor = coord{x: csbi.window.left, y: csbi.window.top} + count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x) + } + procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + case 'K': + n := 0 + if buf.Len() > 0 { + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + } + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + var cursor coord + var count, written dword + switch n { + case 0: + cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} + count = dword(csbi.size.x - csbi.cursorPosition.x) + case 1: + cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y} + count = dword(csbi.size.x - csbi.cursorPosition.x) + case 2: + cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y} + count = dword(csbi.size.x) + } + procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + case 'm': + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + attr := csbi.attributes + cs := buf.String() + if cs == "" { + procSetConsoleTextAttribute.Call(uintptr(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 n == 22 || 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 if len(token) == 5 && token[i+1] == "2" { + var r, g, b int + r, _ = strconv.Atoi(token[i+2]) + g, _ = strconv.Atoi(token[i+3]) + b, _ = strconv.Atoi(token[i+4]) + i += 4 + if r > 127 { + attr |= foregroundRed + } + if g > 127 { + attr |= foregroundGreen + } + if b > 127 { + attr |= foregroundBlue + } + } 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 if len(token) == 5 && token[i+1] == "2" { + var r, g, b int + r, _ = strconv.Atoi(token[i+2]) + g, _ = strconv.Atoi(token[i+3]) + b, _ = strconv.Atoi(token[i+4]) + i += 4 + if r > 127 { + attr |= backgroundRed + } + if g > 127 { + attr |= backgroundGreen + } + if b > 127 { + attr |= backgroundBlue + } + } 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(handle), uintptr(attr)) + } + } + case 'h': + var ci consoleCursorInfo + cs := buf.String() + if cs == "5>" { + procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 0 + procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) + } else if cs == "?25" { + procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 1 + procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) + } else if cs == "?1049" { + if w.althandle == 0 { + h, _, _ := procCreateConsoleScreenBuffer.Call(uintptr(genericRead|genericWrite), 0, 0, uintptr(consoleTextmodeBuffer), 0, 0) + w.althandle = syscall.Handle(h) + if w.althandle != 0 { + handle = w.althandle + } + } + } + case 'l': + var ci consoleCursorInfo + cs := buf.String() + if cs == "5>" { + procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 1 + procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) + } else if cs == "?25" { + procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 0 + procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) + } else if cs == "?1049" { + if w.althandle != 0 { + syscall.CloseHandle(w.althandle) + w.althandle = 0 + handle = w.handle + } + } + case 's': + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + w.oldpos = csbi.cursorPosition + case 'u': + procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos))) + } + } + + return len(data), 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{ + {0x000000, false, false, false, false}, + {0x000080, false, false, true, false}, + {0x008000, false, true, false, false}, + {0x008080, false, true, true, false}, + {0x800000, true, false, false, false}, + {0x800080, true, false, true, false}, + {0x808000, true, true, false, false}, + {0xc0c0c0, true, true, true, false}, + {0x808080, false, false, false, true}, + {0x0000ff, false, false, true, true}, + {0x00ff00, false, true, false, true}, + {0x00ffff, false, true, true, true}, + {0xff0000, true, false, false, true}, + {0xff00ff, true, false, true, true}, + {0xffff00, true, true, false, true}, + {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/go.mod b/vendor/github.com/mattn/go-colorable/go.mod new file mode 100644 index 0000000000..ef3ca9d4c3 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/go.mod @@ -0,0 +1,3 @@ +module github.com/mattn/go-colorable + +require github.com/mattn/go-isatty v0.0.8 diff --git a/vendor/github.com/mattn/go-colorable/go.sum b/vendor/github.com/mattn/go-colorable/go.sum new file mode 100644 index 0000000000..2c12960ec7 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/go.sum @@ -0,0 +1,4 @@ +github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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 0000000000..9721e16f4b --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/noncolorable.go @@ -0,0 +1,55 @@ +package colorable + +import ( + "bytes" + "io" +) + +// NonColorable hold writer but remove escape sequence. +type NonColorable struct { + out io.Writer +} + +// NewNonColorable return new instance of Writer which remove escape sequence from Writer. +func NewNonColorable(w io.Writer) io.Writer { + return &NonColorable{out: w} +} + +// Write write data on console +func (w *NonColorable) Write(data []byte) (n int, err error) { + er := bytes.NewReader(data) + var bw [1]byte +loop: + for { + c1, err := er.ReadByte() + if err != nil { + break loop + } + if c1 != 0x1b { + bw[0] = c1 + w.out.Write(bw[:]) + continue + } + c2, err := er.ReadByte() + if err != nil { + break loop + } + if c2 != 0x5b { + continue + } + + var buf bytes.Buffer + for { + c, err := er.ReadByte() + if err != nil { + break loop + } + if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { + break + } + buf.Write([]byte(string(c))) + } + } + + return len(data), nil +} diff --git a/vendor/github.com/mattn/go-isatty/.travis.yml b/vendor/github.com/mattn/go-isatty/.travis.yml new file mode 100644 index 0000000000..5597e026dd --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/.travis.yml @@ -0,0 +1,13 @@ +language: go +go: + - tip + +os: + - linux + - osx + +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -repotoken 3gHdORO5k5ziZcWMBxnd9LrMZaJs8m9x5 diff --git a/vendor/github.com/mattn/go-isatty/LICENSE b/vendor/github.com/mattn/go-isatty/LICENSE new file mode 100644 index 0000000000..65dc692b6b --- /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 0000000000..1e69004bb0 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/README.md @@ -0,0 +1,50 @@ +# go-isatty + +[![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty) +[![Build Status](https://travis-ci.org/mattn/go-isatty.svg?branch=master)](https://travis-ci.org/mattn/go-isatty) +[![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master) +[![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/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 if isatty.IsCygwinTerminal(os.Stdout.Fd()) { + fmt.Println("Is Cygwin/MSYS2 Terminal") + } else { + fmt.Println("Is Not Terminal") + } +} +``` + +## Installation + +``` +$ go get github.com/mattn/go-isatty +``` + +## License + +MIT + +## Author + +Yasuhiro Matsumoto (a.k.a mattn) + +## Thanks + +* k-takata: base idea for IsCygwinTerminal + + https://github.com/k-takata/go-iscygpty 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 0000000000..17d4f90ebc --- /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/go.mod b/vendor/github.com/mattn/go-isatty/go.mod new file mode 100644 index 0000000000..f310320c33 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/go.mod @@ -0,0 +1,3 @@ +module github.com/mattn/go-isatty + +require golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 diff --git a/vendor/github.com/mattn/go-isatty/go.sum b/vendor/github.com/mattn/go-isatty/go.sum new file mode 100644 index 0000000000..426c8973c0 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/vendor/github.com/mattn/go-isatty/isatty_android.go b/vendor/github.com/mattn/go-isatty/isatty_android.go new file mode 100644 index 0000000000..d3567cb5bf --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_android.go @@ -0,0 +1,23 @@ +// +build android + +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 +} + +// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 +// terminal. This is also always false on this environment. +func IsCygwinTerminal(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 0000000000..07e93039db --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_bsd.go @@ -0,0 +1,24 @@ +// +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 +} + +// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 +// terminal. This is also always false on this environment. +func IsCygwinTerminal(fd uintptr) bool { + return false +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_others.go b/vendor/github.com/mattn/go-isatty/isatty_others.go new file mode 100644 index 0000000000..ff714a3761 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_others.go @@ -0,0 +1,15 @@ +// +build appengine js nacl + +package isatty + +// IsTerminal returns true if the file descriptor is terminal which +// is always false on js and appengine classic which is a sandboxed PaaS. +func IsTerminal(fd uintptr) bool { + return false +} + +// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 +// terminal. This is also always false on this environment. +func IsCygwinTerminal(fd uintptr) bool { + return false +} 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 0000000000..bdd5c79a07 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_solaris.go @@ -0,0 +1,22 @@ +// +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 +} + +// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 +// terminal. This is also always false on this environment. +func IsCygwinTerminal(fd uintptr) bool { + return false +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_tcgets.go b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go new file mode 100644 index 0000000000..453b025d0d --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go @@ -0,0 +1,19 @@ +// +build linux aix +// +build !appengine +// +build !android + +package isatty + +import "golang.org/x/sys/unix" + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + _, err := unix.IoctlGetTermios(int(fd), unix.TCGETS) + return err == nil +} + +// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 +// terminal. This is also always false on this environment. +func IsCygwinTerminal(fd uintptr) bool { + return false +} 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 0000000000..af51cbcaa4 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_windows.go @@ -0,0 +1,94 @@ +// +build windows +// +build !appengine + +package isatty + +import ( + "strings" + "syscall" + "unicode/utf16" + "unsafe" +) + +const ( + fileNameInfo uintptr = 2 + fileTypePipe = 3 +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx") + procGetFileType = kernel32.NewProc("GetFileType") +) + +func init() { + // Check if GetFileInformationByHandleEx is available. + if procGetFileInformationByHandleEx.Find() != nil { + procGetFileInformationByHandleEx = nil + } +} + +// 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 +} + +// Check pipe name is used for cygwin/msys2 pty. +// Cygwin/MSYS2 PTY has a name like: +// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master +func isCygwinPipeName(name string) bool { + token := strings.Split(name, "-") + if len(token) < 5 { + return false + } + + if token[0] != `\msys` && token[0] != `\cygwin` { + return false + } + + if token[1] == "" { + return false + } + + if !strings.HasPrefix(token[2], "pty") { + return false + } + + if token[3] != `from` && token[3] != `to` { + return false + } + + if token[4] != "master" { + return false + } + + return true +} + +// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 +// terminal. +func IsCygwinTerminal(fd uintptr) bool { + if procGetFileInformationByHandleEx == nil { + return false + } + + // Cygwin/msys's pty is a pipe. + ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0) + if ft != fileTypePipe || e != 0 { + return false + } + + var buf [2 + syscall.MAX_PATH]uint16 + r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), + 4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)), + uintptr(len(buf)*2), 0, 0) + if r == 0 || e != 0 { + return false + } + + l := *(*uint32)(unsafe.Pointer(&buf)) + return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2]))) +} diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index a07ee49ea3..4bb86aa0fe 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -994,6 +994,20 @@ func SetsockoptIPMreqn(fd, level, opt int, mreq *IPMreqn) (err error) { return setsockopt(fd, level, opt, unsafe.Pointer(mreq), unsafe.Sizeof(*mreq)) } +// SetsockoptSockFprog attaches a classic BPF or an extended BPF program to a +// socket to filter incoming packets. See 'man 7 socket' for usage information. +func SetsockoptSockFprog(fd, level, opt int, fprog *SockFprog) error { + return setsockopt(fd, level, opt, unsafe.Pointer(fprog), unsafe.Sizeof(*fprog)) +} + +func SetsockoptCanRawFilter(fd, level, opt int, filter []CanFilter) error { + var p unsafe.Pointer + if len(filter) > 0 { + p = unsafe.Pointer(&filter[0]) + } + return setsockopt(fd, level, opt, p, uintptr(len(filter)*SizeofCanFilter)) +} + // Keyctl Commands (http://man7.org/linux/man-pages/man2/keyctl.2.html) // KeyctlInt calls keyctl commands in which each argument is an int. diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_386.go b/vendor/golang.org/x/sys/unix/ztypes_linux_386.go index d262150cc0..3e9c18e681 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_386.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_386.go @@ -405,6 +405,11 @@ type TCPInfo struct { Total_retrans uint32 } +type CanFilter struct { + Id uint32 + Mask uint32 +} + const ( SizeofSockaddrInet4 = 0x10 SizeofSockaddrInet6 = 0x1c @@ -434,6 +439,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0x68 + SizeofCanFilter = 0x8 ) const ( diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go index e492caacda..14365ff6cf 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go @@ -406,6 +406,11 @@ type TCPInfo struct { Total_retrans uint32 } +type CanFilter struct { + Id uint32 + Mask uint32 +} + const ( SizeofSockaddrInet4 = 0x10 SizeofSockaddrInet6 = 0x1c @@ -435,6 +440,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0x68 + SizeofCanFilter = 0x8 ) const ( diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go b/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go index ad4342156e..80ad473c61 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go @@ -409,6 +409,11 @@ type TCPInfo struct { Total_retrans uint32 } +type CanFilter struct { + Id uint32 + Mask uint32 +} + const ( SizeofSockaddrInet4 = 0x10 SizeofSockaddrInet6 = 0x1c @@ -438,6 +443,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0x68 + SizeofCanFilter = 0x8 ) const ( diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go index ef76a362d9..20e78cc1f6 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go @@ -407,6 +407,11 @@ type TCPInfo struct { Total_retrans uint32 } +type CanFilter struct { + Id uint32 + Mask uint32 +} + const ( SizeofSockaddrInet4 = 0x10 SizeofSockaddrInet6 = 0x1c @@ -436,6 +441,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0x68 + SizeofCanFilter = 0x8 ) const ( diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go index dbf05903d4..bdeb0cb24f 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go @@ -408,6 +408,11 @@ type TCPInfo struct { Total_retrans uint32 } +type CanFilter struct { + Id uint32 + Mask uint32 +} + const ( SizeofSockaddrInet4 = 0x10 SizeofSockaddrInet6 = 0x1c @@ -437,6 +442,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0x68 + SizeofCanFilter = 0x8 ) const ( diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go index 1b7e670793..2d3f591129 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go @@ -407,6 +407,11 @@ type TCPInfo struct { Total_retrans uint32 } +type CanFilter struct { + Id uint32 + Mask uint32 +} + const ( SizeofSockaddrInet4 = 0x10 SizeofSockaddrInet6 = 0x1c @@ -436,6 +441,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0x68 + SizeofCanFilter = 0x8 ) const ( diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go index 57379005b5..5fb57ff2a3 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go @@ -407,6 +407,11 @@ type TCPInfo struct { Total_retrans uint32 } +type CanFilter struct { + Id uint32 + Mask uint32 +} + const ( SizeofSockaddrInet4 = 0x10 SizeofSockaddrInet6 = 0x1c @@ -436,6 +441,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0x68 + SizeofCanFilter = 0x8 ) const ( diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go index 0e88bf47bc..b46b26f6ce 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go @@ -408,6 +408,11 @@ type TCPInfo struct { Total_retrans uint32 } +type CanFilter struct { + Id uint32 + Mask uint32 +} + const ( SizeofSockaddrInet4 = 0x10 SizeofSockaddrInet6 = 0x1c @@ -437,6 +442,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0x68 + SizeofCanFilter = 0x8 ) const ( diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go index 5ac91b3f7a..e14e3c90ab 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go @@ -408,6 +408,11 @@ type TCPInfo struct { Total_retrans uint32 } +type CanFilter struct { + Id uint32 + Mask uint32 +} + const ( SizeofSockaddrInet4 = 0x10 SizeofSockaddrInet6 = 0x1c @@ -437,6 +442,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0x68 + SizeofCanFilter = 0x8 ) const ( diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go index 1e59b45068..2332e8fd12 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go @@ -408,6 +408,11 @@ type TCPInfo struct { Total_retrans uint32 } +type CanFilter struct { + Id uint32 + Mask uint32 +} + const ( SizeofSockaddrInet4 = 0x10 SizeofSockaddrInet6 = 0x1c @@ -437,6 +442,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0x68 + SizeofCanFilter = 0x8 ) const ( diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go index 508885f11f..efec4f8193 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go @@ -407,6 +407,11 @@ type TCPInfo struct { Total_retrans uint32 } +type CanFilter struct { + Id uint32 + Mask uint32 +} + const ( SizeofSockaddrInet4 = 0x10 SizeofSockaddrInet6 = 0x1c @@ -436,6 +441,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0x68 + SizeofCanFilter = 0x8 ) const ( diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go index d315f2c3a7..71cc23f2e1 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go @@ -406,6 +406,11 @@ type TCPInfo struct { Total_retrans uint32 } +type CanFilter struct { + Id uint32 + Mask uint32 +} + const ( SizeofSockaddrInet4 = 0x10 SizeofSockaddrInet6 = 0x1c @@ -435,6 +440,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0x68 + SizeofCanFilter = 0x8 ) const ( diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go index a1a9279c22..48805ba195 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go @@ -410,6 +410,11 @@ type TCPInfo struct { Total_retrans uint32 } +type CanFilter struct { + Id uint32 + Mask uint32 +} + const ( SizeofSockaddrInet4 = 0x10 SizeofSockaddrInet6 = 0x1c @@ -439,6 +444,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0x68 + SizeofCanFilter = 0x8 ) const ( diff --git a/vendor/golang.org/x/sys/windows/security_windows.go b/vendor/golang.org/x/sys/windows/security_windows.go index 4f17a3331f..9f946da6fe 100644 --- a/vendor/golang.org/x/sys/windows/security_windows.go +++ b/vendor/golang.org/x/sys/windows/security_windows.go @@ -149,7 +149,7 @@ const ( DOMAIN_ALIAS_RID_REMOTE_DESKTOP_USERS = 0x22b DOMAIN_ALIAS_RID_NETWORK_CONFIGURATION_OPS = 0x22c DOMAIN_ALIAS_RID_INCOMING_FOREST_TRUST_BUILDERS = 0x22d - DOMAIN_ALIAS_RID_MONITORING_USERS = 0X22e + DOMAIN_ALIAS_RID_MONITORING_USERS = 0x22e DOMAIN_ALIAS_RID_LOGGING_USERS = 0x22f DOMAIN_ALIAS_RID_AUTHORIZATIONACCESS = 0x230 DOMAIN_ALIAS_RID_TS_LICENSE_SERVERS = 0x231 diff --git a/vendor/modules.txt b/vendor/modules.txt index d58400cdd1..de49116bf3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -6,6 +6,8 @@ github.com/cpuguy83/go-md2man/md2man github.com/davecgh/go-spew/spew # github.com/evanphx/json-patch v4.5.0+incompatible github.com/evanphx/json-patch +# github.com/fatih/color v1.7.0 +github.com/fatih/color # github.com/fsnotify/fsnotify v1.4.7 github.com/fsnotify/fsnotify # github.com/ghodss/yaml v1.0.0 @@ -90,6 +92,10 @@ github.com/knative/test-infra/scripts github.com/magiconair/properties # github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a github.com/mattbaird/jsonpatch +# github.com/mattn/go-colorable v0.1.2 +github.com/mattn/go-colorable +# github.com/mattn/go-isatty v0.0.8 +github.com/mattn/go-isatty # github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir # github.com/mitchellh/mapstructure v1.1.2 @@ -135,7 +141,7 @@ golang.org/x/oauth2/google golang.org/x/oauth2/internal golang.org/x/oauth2/jws golang.org/x/oauth2/jwt -# golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a +# golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 golang.org/x/sys/unix golang.org/x/sys/windows # golang.org/x/text v0.3.0 From e67694ba3b2cbbacbde6141c86e33011682dce21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Mon, 8 Jul 2019 21:26:38 +0200 Subject: [PATCH 2/2] chore(service): Rebased on master --- go.sum | 8 ++ pkg/kn/commands/service/service_describe.go | 27 +++---- .../{ => service}/service_describe_compact.go | 23 +++++- .../commands/service/service_describe_test.go | 15 +--- pkg/printers/prefixwriter.go | 16 +++- pkg/serving/client.go | 78 ------------------- pkg/serving/v1alpha1/client.go | 16 ++++ 7 files changed, 74 insertions(+), 109 deletions(-) rename pkg/kn/commands/{ => service}/service_describe_compact.go (88%) delete mode 100644 pkg/serving/client.go diff --git a/go.sum b/go.sum index 330faf3a73..dc12bb0d7c 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 h1:ZktWZesgun21uEDrwW7iEV1zPCGQldM2atlJZ3TdvVM= @@ -186,6 +188,10 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a h1:TpvdAwDAt1K4ANV github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a h1:+J2gw7Bw77w/fbK7wnNJJDKmw1IbWft2Ul5BzrG1Qm8= github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -331,6 +337,8 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/pkg/kn/commands/service/service_describe.go b/pkg/kn/commands/service/service_describe.go index 3a27bc02c5..d818b8a1eb 100644 --- a/pkg/kn/commands/service/service_describe.go +++ b/pkg/kn/commands/service/service_describe.go @@ -26,7 +26,8 @@ import ( "github.com/fatih/color" "github.com/knative/client/pkg/printers" - "github.com/knative/client/pkg/serving" + serving_kn_v1alpha1 "github.com/knative/client/pkg/serving/v1alpha1" + "github.com/knative/pkg/apis" "github.com/knative/pkg/apis/duck/v1beta1" "github.com/knative/serving/pkg/apis/serving/v1alpha1" @@ -95,7 +96,7 @@ func NewServiceDescribeCommand(p *commands.KnParams) *cobra.Command { } serviceName := args[0] - namespace, err := GetNamespace(cmd) + namespace, err := p.GetNamespace(cmd) if err != nil { return err } @@ -116,12 +117,12 @@ func NewServiceDescribeCommand(p *commands.KnParams) *cobra.Command { // Set color option globally color.NoColor = !useColor - service, err := client.Service(serviceName) + service, err := client.GetService(serviceName) if err != nil { return err } // Additional revision related information - revisionDescs, err := getRevisionDescriptions(sClient, service, printAll) + revisionDescs, err := getRevisionDescriptions(client, service, printAll) hipsterMode, err := cmd.Flags().GetBool("hipster") if err != nil { @@ -136,7 +137,7 @@ func NewServiceDescribeCommand(p *commands.KnParams) *cobra.Command { }, } flags := command.Flags() - AddNamespaceFlags(flags, false) + commands.AddNamespaceFlags(flags, false) flags.BoolP("all", "a", false, "don't truncate long information") flags.BoolP("color", "c", false, "use colorful output") flags.Bool("hipster", false, "🤓") @@ -378,7 +379,7 @@ func age(t time.Time) string { // Call the backend to query revisions for the given service and build up // the view objects used for output -func getRevisionDescriptions(client *serving.NamespacedClient, service *v1alpha1.Service, all bool) ([]*revisionDesc, error) { +func getRevisionDescriptions(client serving_kn_v1alpha1.KnClient, service *v1alpha1.Service, all bool) ([]*revisionDesc, error) { revisionDescs := make(map[string]*revisionDesc) trafficTargets := service.Status.Traffic @@ -415,14 +416,14 @@ func orderByConfigurationGeneration(descs map[string]*revisionDesc) []*revisionD return descsList } -func completeWithUntargetedRevisions(client *serving.NamespacedClient, service *v1alpha1.Service, descs map[string]*revisionDesc) error { - revisions, err := client.RevisionsForService(service) +func completeWithUntargetedRevisions(client serving_kn_v1alpha1.KnClient, service *v1alpha1.Service, descs map[string]*revisionDesc) error { + revisions, err := client.ListRevisions(serving_kn_v1alpha1.WithService(service.Name)) if err != nil { return err } - for _, revision := range revisions { + for _, revision := range revisions.Items { if _, ok := descs[revision.Name]; !ok { - descs[revision.Name], err = newRevisionDesc(revision, nil) + descs[revision.Name], err = newRevisionDesc(&revision, nil) if err != nil { return err } @@ -498,20 +499,20 @@ func extractContainer(revision *v1alpha1.Revision) *v1.Container { return revision.Spec.DeprecatedContainer } -func extractRevisionFromTarget(client *serving.NamespacedClient, target v1alpha1.TrafficTarget) (*v1alpha1.Revision, error) { +func extractRevisionFromTarget(client serving_kn_v1alpha1.KnClient, target v1alpha1.TrafficTarget) (*v1alpha1.Revision, error) { var revisionName = target.RevisionName if revisionName == "" { configurationName := target.ConfigurationName if configurationName == "" { return nil, fmt.Errorf("neither RevisionName nor ConfigurationName set") } - configuration, err := client.Configuration(configurationName) + configuration, err := client.GetConfiguration(configurationName) if err != nil { return nil, err } revisionName = configuration.Status.LatestCreatedRevisionName } - return client.Revision(revisionName) + return client.GetRevision(revisionName) } func extractURL(service *v1alpha1.Service) string { diff --git a/pkg/kn/commands/service_describe_compact.go b/pkg/kn/commands/service/service_describe_compact.go similarity index 88% rename from pkg/kn/commands/service_describe_compact.go rename to pkg/kn/commands/service/service_describe_compact.go index c5538f8ea4..c2ece9aebf 100644 --- a/pkg/kn/commands/service_describe_compact.go +++ b/pkg/kn/commands/service/service_describe_compact.go @@ -1,14 +1,29 @@ -package commands +// Copyright © 2019 The Knative 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 service import ( "fmt" + "io" + "strconv" + "strings" + "github.com/fatih/color" "github.com/knative/client/pkg/printers" "github.com/knative/serving/pkg/apis/serving/v1alpha1" - "io" v1 "k8s.io/api/core/v1" - "strconv" - "strings" ) var emojiMap = map[string]string{ diff --git a/pkg/kn/commands/service/service_describe_test.go b/pkg/kn/commands/service/service_describe_test.go index 3c8c9157d6..36f3536a04 100644 --- a/pkg/kn/commands/service/service_describe_test.go +++ b/pkg/kn/commands/service/service_describe_test.go @@ -15,31 +15,20 @@ package service import ( - "bytes" "regexp" "strings" - "encoding/json" "testing" duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" - "github.com/knative/client/pkg/kn/commands" "github.com/knative/serving/pkg/apis/serving/v1alpha1" - serving "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1" - "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake" - "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" clienttesting "k8s.io/client-go/testing" + + "github.com/knative/client/pkg/kn/commands" ) func fakeServiceDescribe(args []string, response *v1alpha1.Service) (action clienttesting.Action, output string, err error) { - buf := new(bytes.Buffer) - fakeServing := &fake.FakeServingV1alpha1{Fake: &clienttesting.Fake{}} - cmd := NewKnCommand(KnParams{ - Output: buf, - ServingFactory: func() (serving.ServingV1alpha1Interface, error) { return fakeServing, nil }, - }) -func fakeServiceDescribe(args []string, response *v1alpha1.Service) (action client_testing.Action, output string, err error) { knParams := &commands.KnParams{} cmd, fakeServing, buf := commands.CreateTestKnCommand(NewServiceCommand(knParams), knParams) fakeServing.AddReactor("*", "*", diff --git a/pkg/printers/prefixwriter.go b/pkg/printers/prefixwriter.go index 157bbdedfd..50daa855d0 100644 --- a/pkg/printers/prefixwriter.go +++ b/pkg/printers/prefixwriter.go @@ -1,3 +1,17 @@ +// Copyright © 2019 The Knative 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 printers import ( @@ -57,7 +71,7 @@ func (pw *prefixWriter) Write(level int, format string, a ...interface{}) { func (pw *prefixWriter) WriteCols(level int, cols ...string) { ss := make([]string, len(cols)) - for i, _ := range cols { + for i := range cols { ss[i] = "%s" } format := strings.Join(ss, "\t") diff --git a/pkg/serving/client.go b/pkg/serving/client.go deleted file mode 100644 index 40320e828a..0000000000 --- a/pkg/serving/client.go +++ /dev/null @@ -1,78 +0,0 @@ -package serving - -import ( - "fmt" - "github.com/knative/serving/pkg/apis/serving/v1alpha1" - client_v1alpha1 "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type NamespacedClient struct { - client client_v1alpha1.ServingV1alpha1Interface - namespace string -} - -func NewNamespacedClient(client client_v1alpha1.ServingV1alpha1Interface, namespace string) *NamespacedClient { - return &NamespacedClient{ - client: client, - namespace: namespace, - } -} - -func (cl *NamespacedClient) Service(name string) (*v1alpha1.Service, error) { - service, err := cl.client.Services(cl.namespace).Get(name, v1.GetOptions{}) - if err != nil { - return nil, err - } - service.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{ - Group: "knative.dev", - Version: "v1alpha1", - Kind: "Service"}) - return service, nil -} - -func (cl *NamespacedClient) Revision(name string) (*v1alpha1.Revision, error) { - revision, err := cl.client.Revisions(cl.namespace).Get(name, v1.GetOptions{}) - if err != nil { - return nil, err - } - updateRevisionWithGVK(revision) - return revision, nil -} - -func (cl *NamespacedClient) Configuration(name string) (*v1alpha1.Configuration, error) { - cofiguration, err := cl.client.Configurations(cl.namespace).Get(name, v1.GetOptions{}) - if err != nil { - return nil, err - } - cofiguration.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{ - Group: "knative.dev", - Version: "v1alpha1", - Kind: "Configuration"}) - return cofiguration, nil -} - -func (cl *NamespacedClient) RevisionsForService(service *v1alpha1.Service) ([]*v1alpha1.Revision, error) { - labelSelector := fmt.Sprintf("serving.knative.dev/service=%s", service.Name) - revisions, err := cl.client.Revisions(cl.namespace).List(v1.ListOptions{LabelSelector: labelSelector}) - if err != nil { - return nil, err - } - revisionSlice := make([]*v1alpha1.Revision, len(revisions.Items)) - for idx, _ := range revisions.Items { - revision := revisions.Items[idx].DeepCopy() - updateRevisionWithGVK(revision) - revisionSlice[idx] = revision - } - return revisionSlice, nil -} - -// ============================================================================ - -func updateRevisionWithGVK(revision *v1alpha1.Revision) { - revision.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{ - Group: "knative.dev", - Version: "v1alpha1", - Kind: "Revision"}) -} diff --git a/pkg/serving/v1alpha1/client.go b/pkg/serving/v1alpha1/client.go index f08a7d82dc..a06022b87a 100644 --- a/pkg/serving/v1alpha1/client.go +++ b/pkg/serving/v1alpha1/client.go @@ -54,6 +54,9 @@ type KnClient interface { // Wait for a service to become ready, but not longer than provided timeout WaitForService(name string, timeout time.Duration) error + // Get a configuration by name + GetConfiguration(name string) (*v1alpha1.Configuration, error) + // Get a revision by name GetRevision(name string) (*v1alpha1.Revision, error) @@ -192,6 +195,19 @@ func (cl *knClient) WaitForService(name string, timeout time.Duration) error { return waitForReady.Wait(name, timeout) } +// Get the configuration for a service +func (cl *knClient) GetConfiguration(name string) (*v1alpha1.Configuration, error) { + configuration, err := cl.client.Configurations(cl.namespace).Get(name, v1.GetOptions{}) + if err != nil { + return nil, err + } + err = updateServingGvk(configuration) + if err != nil { + return nil, err + } + return configuration, nil +} + // Get a revision by name func (cl *knClient) GetRevision(name string) (*v1alpha1.Revision, error) { revision, err := cl.client.Revisions(cl.namespace).Get(name, v1.GetOptions{})