From 8438089e96aec602ab9bb9374cc63d4e7ebea339 Mon Sep 17 00:00:00 2001 From: Fabiano Franz Date: Fri, 7 Oct 2016 16:21:44 -0300 Subject: [PATCH 1/7] Add heredoc and go-wordwrap dependencies --- Godeps/Godeps.json | 8 ++ Godeps/LICENSES | 58 ++++++++++++ vendor/github.com/MakeNowJust/heredoc/LICENSE | 21 +++++ .../github.com/MakeNowJust/heredoc/README.md | 53 +++++++++++ .../github.com/MakeNowJust/heredoc/heredoc.go | 89 +++++++++++++++++++ .../mitchellh/go-wordwrap/LICENSE.md | 21 +++++ .../mitchellh/go-wordwrap/README.md | 39 ++++++++ .../mitchellh/go-wordwrap/wordwrap.go | 73 +++++++++++++++ 8 files changed, 362 insertions(+) create mode 100644 vendor/github.com/MakeNowJust/heredoc/LICENSE create mode 100644 vendor/github.com/MakeNowJust/heredoc/README.md create mode 100644 vendor/github.com/MakeNowJust/heredoc/heredoc.go create mode 100644 vendor/github.com/mitchellh/go-wordwrap/LICENSE.md create mode 100644 vendor/github.com/mitchellh/go-wordwrap/README.md create mode 100644 vendor/github.com/mitchellh/go-wordwrap/wordwrap.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 5d7d1711c83..7e42442b34e 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -65,6 +65,10 @@ "Comment": "v7.0.6-4-g2492d97", "Rev": "2492d97b402e00797833c03ac5fa1c572c7bb29a" }, + { + "ImportPath": "github.com/MakeNowJust/heredoc", + "Rev": "1d91351acdc1cb2f2c995864674b754134b86ca7" + }, { "ImportPath": "github.com/Microsoft/go-winio", "Comment": "v0.1.0", @@ -1537,6 +1541,10 @@ "Comment": "v2.1.1-5-g1b4ae6f", "Rev": "1b4ae6fb4e77b095934d4430860ff202060169f8" }, + { + "ImportPath": "github.com/mitchellh/go-wordwrap", + "Rev": "ad45545899c7b13c020ea92b2072220eefad42b8" + }, { "ImportPath": "github.com/mitchellh/mapstructure", "Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf" diff --git a/Godeps/LICENSES b/Godeps/LICENSES index f5185206aee..7794f9956d2 100644 --- a/Godeps/LICENSES +++ b/Godeps/LICENSES @@ -51179,6 +51179,35 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ================================================================================ +================================================================================ += vendor/github.com/MakeNowJust/heredoc licensed under: = + +The MIT License (MIT) + +Copyright (c) 2014 TSUYUSATO Kitsune + +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. + += vendor/github.com/MakeNowJust/heredoc/LICENSE 15e1c8f1d3c204c05f71630afacbc92b - +================================================================================ + + ================================================================================ = vendor/github.com/matttproud/golang_protobuf_extensions/pbutil licensed under: = @@ -52706,6 +52735,35 @@ Apache License ================================================================================ +================================================================================ += vendor/github.com/mitchellh/go-wordwrap licensed under: = + +The MIT License (MIT) + +Copyright (c) 2014 Mitchell Hashimoto + +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. + += vendor/github.com/mitchellh/go-wordwrap/LICENSE.md 56da355a12d4821cda57b8f23ec34bc4 - +================================================================================ + + ================================================================================ = vendor/github.com/mitchellh/mapstructure licensed under: = diff --git a/vendor/github.com/MakeNowJust/heredoc/LICENSE b/vendor/github.com/MakeNowJust/heredoc/LICENSE new file mode 100644 index 00000000000..dd5aa7e8a40 --- /dev/null +++ b/vendor/github.com/MakeNowJust/heredoc/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 TSUYUSATO Kitsune + +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/MakeNowJust/heredoc/README.md b/vendor/github.com/MakeNowJust/heredoc/README.md new file mode 100644 index 00000000000..df12b43db63 --- /dev/null +++ b/vendor/github.com/MakeNowJust/heredoc/README.md @@ -0,0 +1,53 @@ +#heredoc [![Build Status](https://drone.io/github.com/MakeNowJust/heredoc/status.png)](https://drone.io/github.com/MakeNowJust/heredoc/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](https://gowalker.org/github.com/MakeNowJust/heredoc) + +##About + +Package heredoc provides the here-document with keeping indent. + +##Install + +```console +$ go get github.com/MakeNowJust/heredoc +``` + +##Import + +```go +// usual +import "github.com/MakeNowJust/heredoc" +// shortcuts +import . "github.com/MakeNowJust/heredoc/dot" +``` + +##Example + +```go +package main + +import ( + "fmt" + . "github.com/MakeNowJust/heredoc/dot" +) + +func main() { + fmt.Println(D(` + Lorem ipsum dolor sit amet, consectetur adipisicing elit, + sed do eiusmod tempor incididunt ut labore et dolore magna + aliqua. Ut enim ad minim veniam, ... + `)) + // Output: + // Lorem ipsum dolor sit amet, consectetur adipisicing elit, + // sed do eiusmod tempor incididunt ut labore et dolore magna + // aliqua. Ut enim ad minim veniam, ... + // +} +``` + +##API Document + + - [Go Walker - github.com/MakeNowJust/heredoc](https://gowalker.org/github.com/MakeNowJust/heredoc) + - [Go Walker - github.com/MakeNowJust/heredoc/dot](https://gowalker.org/github.com/MakeNowJust/heredoc/dot) + +##License + +This software is released under the MIT License, see LICENSE. diff --git a/vendor/github.com/MakeNowJust/heredoc/heredoc.go b/vendor/github.com/MakeNowJust/heredoc/heredoc.go new file mode 100644 index 00000000000..3978e30dafd --- /dev/null +++ b/vendor/github.com/MakeNowJust/heredoc/heredoc.go @@ -0,0 +1,89 @@ +// Copyright (c) 2014 TSUYUSATO Kitsune +// This software is released under the MIT License. +// http://opensource.org/licenses/mit-license.php + +// Package heredoc provides the here-document with keeping indent. +// +// Golang supports raw-string syntax. +// doc := ` +// Foo +// Bar +// ` +// But raw-string cannot recognize indent. Thus such content is indented string, equivalent to +// "\n\tFoo\n\tBar\n" +// I dont't want this! +// +// However this problem is solved by package heredoc. +// doc := heredoc.Doc(` +// Foo +// Bar +// `) +// It is equivalent to +// "Foo\nBar\n" +package heredoc + +import ( + "fmt" + "strings" + "unicode" +) + +// heredoc.Doc retutns unindented string as here-document. +// +// Process of making here-document: +// 1. Find most little indent size. (Skip empty lines) +// 2. Remove this indents of lines. +func Doc(raw string) string { + skipFirstLine := false + if raw[0] == '\n' { + raw = raw[1:] + } else { + skipFirstLine = true + } + + minIndentSize := int(^uint(0) >> 1) // Max value of type int + lines := strings.Split(raw, "\n") + + // 1. + for i, line := range lines { + if i == 0 && skipFirstLine { + continue + } + + indentSize := 0 + for _, r := range []rune(line) { + if unicode.IsSpace(r) { + indentSize += 1 + } else { + break + } + } + + if len(line) == indentSize { + if i == len(lines)-1 && indentSize < minIndentSize { + lines[i] = "" + } + } else if indentSize < minIndentSize { + minIndentSize = indentSize + } + } + + // 2. + for i, line := range lines { + if i == 0 && skipFirstLine { + continue + } + + if len(lines[i]) >= minIndentSize { + lines[i] = line[minIndentSize:] + } + } + + return strings.Join(lines, "\n") +} + +// heredoc.Docf returns unindented and formatted string as here-document. +// This format is same with package fmt's format. +func Docf(raw string, args ...interface{}) string { + return fmt.Sprintf(Doc(raw), args...) +} diff --git a/vendor/github.com/mitchellh/go-wordwrap/LICENSE.md b/vendor/github.com/mitchellh/go-wordwrap/LICENSE.md new file mode 100644 index 00000000000..22985159044 --- /dev/null +++ b/vendor/github.com/mitchellh/go-wordwrap/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Mitchell Hashimoto + +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/mitchellh/go-wordwrap/README.md b/vendor/github.com/mitchellh/go-wordwrap/README.md new file mode 100644 index 00000000000..60ae3117008 --- /dev/null +++ b/vendor/github.com/mitchellh/go-wordwrap/README.md @@ -0,0 +1,39 @@ +# go-wordwrap + +`go-wordwrap` (Golang package: `wordwrap`) is a package for Go that +automatically wraps words into multiple lines. The primary use case for this +is in formatting CLI output, but of course word wrapping is a generally useful +thing to do. + +## Installation and Usage + +Install using `go get github.com/mitchellh/go-wordwrap`. + +Full documentation is available at +http://godoc.org/github.com/mitchellh/go-wordwrap + +Below is an example of its usage ignoring errors: + +```go +wrapped := wordwrap.WrapString("foo bar baz", 3) +fmt.Println(wrapped) +``` + +Would output: + +``` +foo +bar +baz +``` + +## Word Wrap Algorithm + +This library doesn't use any clever algorithm for word wrapping. The wrapping +is actually very naive: whenever there is whitespace or an explicit linebreak. +The goal of this library is for word wrapping CLI output, so the input is +typically pretty well controlled human language. Because of this, the naive +approach typically works just fine. + +In the future, we'd like to make the algorithm more advanced. We would do +so without breaking the API. diff --git a/vendor/github.com/mitchellh/go-wordwrap/wordwrap.go b/vendor/github.com/mitchellh/go-wordwrap/wordwrap.go new file mode 100644 index 00000000000..ac67205bc2e --- /dev/null +++ b/vendor/github.com/mitchellh/go-wordwrap/wordwrap.go @@ -0,0 +1,73 @@ +package wordwrap + +import ( + "bytes" + "unicode" +) + +// WrapString wraps the given string within lim width in characters. +// +// Wrapping is currently naive and only happens at white-space. A future +// version of the library will implement smarter wrapping. This means that +// pathological cases can dramatically reach past the limit, such as a very +// long word. +func WrapString(s string, lim uint) string { + // Initialize a buffer with a slightly larger size to account for breaks + init := make([]byte, 0, len(s)) + buf := bytes.NewBuffer(init) + + var current uint + var wordBuf, spaceBuf bytes.Buffer + + for _, char := range s { + if char == '\n' { + if wordBuf.Len() == 0 { + if current+uint(spaceBuf.Len()) > lim { + current = 0 + } else { + current += uint(spaceBuf.Len()) + spaceBuf.WriteTo(buf) + } + spaceBuf.Reset() + } else { + current += uint(spaceBuf.Len() + wordBuf.Len()) + spaceBuf.WriteTo(buf) + spaceBuf.Reset() + wordBuf.WriteTo(buf) + wordBuf.Reset() + } + buf.WriteRune(char) + current = 0 + } else if unicode.IsSpace(char) { + if spaceBuf.Len() == 0 || wordBuf.Len() > 0 { + current += uint(spaceBuf.Len() + wordBuf.Len()) + spaceBuf.WriteTo(buf) + spaceBuf.Reset() + wordBuf.WriteTo(buf) + wordBuf.Reset() + } + + spaceBuf.WriteRune(char) + } else { + + wordBuf.WriteRune(char) + + if current+uint(spaceBuf.Len()+wordBuf.Len()) > lim && uint(wordBuf.Len()) < lim { + buf.WriteRune('\n') + current = 0 + spaceBuf.Reset() + } + } + } + + if wordBuf.Len() == 0 { + if current+uint(spaceBuf.Len()) <= lim { + spaceBuf.WriteTo(buf) + } + } else { + spaceBuf.WriteTo(buf) + wordBuf.WriteTo(buf) + } + + return buf.String() +} From 6d6aeb0027425ad7c627a494eb9c947604238209 Mon Sep 17 00:00:00 2001 From: Fabiano Franz Date: Fri, 7 Oct 2016 16:22:55 -0300 Subject: [PATCH 2/7] Add responsive writers which adjust to terminal sizes --- pkg/util/term/term_writer.go | 124 ++++++++++++++++++++++++++++++ pkg/util/term/term_writer_test.go | 98 +++++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 pkg/util/term/term_writer.go create mode 100644 pkg/util/term/term_writer_test.go diff --git a/pkg/util/term/term_writer.go b/pkg/util/term/term_writer.go new file mode 100644 index 00000000000..2d72d1e4525 --- /dev/null +++ b/pkg/util/term/term_writer.go @@ -0,0 +1,124 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package term + +import ( + "io" + "os" + + "github.com/docker/docker/pkg/term" + wordwrap "github.com/mitchellh/go-wordwrap" +) + +type wordWrapWriter struct { + limit uint + writer io.Writer +} + +// NewResponsiveWriter creates a Writer that detects the column width of the +// terminal we are in, and adjusts every line width to fit and use recommended +// terminal sizes for better readability. Does proper word wrapping automatically. +// if terminal width >= 120 columns use 120 columns +// if terminal width >= 100 columns use 100 columns +// if terminal width >= 80 columns use 80 columns +// In case we're not in a terminal or if it's smaller than 80 columns width, +// doesn't do any wrapping. +func NewResponsiveWriter(w io.Writer) io.Writer { + file, ok := w.(*os.File) + if !ok { + return w + } + fd := file.Fd() + if !term.IsTerminal(fd) { + return w + } + + terminalSize := GetSize(fd) + if terminalSize == nil { + return w + } + + var limit uint + switch { + case terminalSize.Width >= 120: + limit = 120 + case terminalSize.Width >= 100: + limit = 100 + case terminalSize.Width >= 80: + limit = 80 + } + + return NewWordWrapWriter(w, limit) +} + +// NewWordWrapWriter is a Writer that supports a limit of characters on every line +// and does auto word wrapping that respects that limit. +func NewWordWrapWriter(w io.Writer, limit uint) io.Writer { + return &wordWrapWriter{ + limit: limit, + writer: w, + } +} + +func (w wordWrapWriter) Write(p []byte) (nn int, err error) { + if w.limit == 0 { + return w.writer.Write(p) + } + original := string(p) + wrapped := wordwrap.WrapString(original, w.limit) + return w.writer.Write([]byte(wrapped)) +} + +// NewPunchCardWriter is a NewWordWrapWriter that limits the line width to 80 columns. +func NewPunchCardWriter(w io.Writer) io.Writer { + return NewWordWrapWriter(w, 80) +} + +type maxWidthWriter struct { + maxWidth uint + currentWidth uint + written uint + writer io.Writer +} + +// NewMaxWidthWriter is a Writer that supports a limit of characters on every +// line, but doesn't do any word wrapping automatically. +func NewMaxWidthWriter(w io.Writer, maxWidth uint) io.Writer { + return &maxWidthWriter{ + maxWidth: maxWidth, + writer: w, + } +} + +func (m maxWidthWriter) Write(p []byte) (nn int, err error) { + for _, b := range p { + if m.currentWidth == m.maxWidth { + m.writer.Write([]byte{'\n'}) + m.currentWidth = 0 + } + if b == '\n' { + m.currentWidth = 0 + } + _, err := m.writer.Write([]byte{b}) + if err != nil { + return int(m.written), err + } + m.written++ + m.currentWidth++ + } + return len(p), nil +} diff --git a/pkg/util/term/term_writer_test.go b/pkg/util/term/term_writer_test.go new file mode 100644 index 00000000000..7d22d6c8887 --- /dev/null +++ b/pkg/util/term/term_writer_test.go @@ -0,0 +1,98 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package term + +import ( + "bytes" + "strings" + "testing" +) + +const test = "Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube Kube" + +func TestWordWrapWriter(t *testing.T) { + testcases := map[string]struct { + input string + maxWidth uint + }{ + "max 10": {input: test, maxWidth: 10}, + "max 80": {input: test, maxWidth: 80}, + "max 120": {input: test, maxWidth: 120}, + "max 5000": {input: test, maxWidth: 5000}, + } + for k, tc := range testcases { + b := bytes.NewBufferString("") + w := NewWordWrapWriter(b, tc.maxWidth) + _, err := w.Write([]byte(tc.input)) + if err != nil { + t.Errorf("%s: Unexpected error: %v", k, err) + } + result := b.String() + if !strings.Contains(result, "Kube") { + t.Errorf("%s: Expected to contain \"Kube\"", k) + } + if len(result) < len(tc.input) { + t.Errorf("%s: Unexpectedly short string, got %d wanted at least %d chars: %q", k, len(result), len(tc.input), result) + } + for _, line := range strings.Split(result, "\n") { + if len(line) > int(tc.maxWidth) { + t.Errorf("%s: Every line must be at most %d chars long, got %d: %q", k, tc.maxWidth, len(line), line) + } + } + for _, word := range strings.Split(result, " ") { + if !strings.Contains(word, "Kube") { + t.Errorf("%s: Unexpected broken word: %q", k, word) + } + } + } +} + +func TestMaxWidthWriter(t *testing.T) { + testcases := map[string]struct { + input string + maxWidth uint + }{ + "max 10": {input: test, maxWidth: 10}, + "max 80": {input: test, maxWidth: 80}, + "max 120": {input: test, maxWidth: 120}, + "max 5000": {input: test, maxWidth: 5000}, + } + for k, tc := range testcases { + b := bytes.NewBufferString("") + w := NewMaxWidthWriter(b, tc.maxWidth) + _, err := w.Write([]byte(tc.input)) + if err != nil { + t.Errorf("%s: Unexpected error: %v", k, err) + } + result := b.String() + if !strings.Contains(result, "Kube") { + t.Errorf("%s: Expected to contain \"Kube\"", k) + } + if len(result) < len(tc.input) { + t.Errorf("%s: Unexpectedly short string, got %d wanted at least %d chars: %q", k, len(result), len(tc.input), result) + } + lines := strings.Split(result, "\n") + for i, line := range lines { + if len(line) > int(tc.maxWidth) { + t.Errorf("%s: Every line must be at most %d chars long, got %d: %q", k, tc.maxWidth, len(line), line) + } + if i < len(lines)-1 && len(line) != int(tc.maxWidth) { + t.Errorf("%s: Lines except the last one are expected to be exactly %d chars long, got %d: %q", k, tc.maxWidth, len(line), line) + } + } + } +} From 6f66c0eafa784527f46fb3259d1e9bc014d5e19d Mon Sep 17 00:00:00 2001 From: Fabiano Franz Date: Fri, 7 Oct 2016 16:50:39 -0300 Subject: [PATCH 3/7] Add normalizers for cmd examples and descriptions --- pkg/kubectl/cmd/templates/markdown.go | 145 +++++++++++++++++++++++ pkg/kubectl/cmd/templates/normalizers.go | 91 ++++++++++++++ pkg/kubectl/cmd/templates/templates.go | 2 +- 3 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 pkg/kubectl/cmd/templates/markdown.go create mode 100644 pkg/kubectl/cmd/templates/normalizers.go diff --git a/pkg/kubectl/cmd/templates/markdown.go b/pkg/kubectl/cmd/templates/markdown.go new file mode 100644 index 00000000000..1a19f89fd4c --- /dev/null +++ b/pkg/kubectl/cmd/templates/markdown.go @@ -0,0 +1,145 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "bytes" + "fmt" + "strings" + + "github.com/russross/blackfriday" +) + +const linebreak = "\n" + +// ASCIIRenderer implements blackfriday.Renderer +var _ blackfriday.Renderer = &ASCIIRenderer{} + +// ASCIIRenderer is a blackfriday.Renderer intended for rendering markdown +// documents as plain text, well suited for human reading on terminals. +type ASCIIRenderer struct { + Indentation string + + listItemCount uint + listLevel uint +} + +// NormalText gets a text chunk *after* the markdown syntax was already +// processed and does a final cleanup on things we don't expect here, like +// removing linebreaks on things that are not a paragraph break (auto unwrap). +func (r *ASCIIRenderer) NormalText(out *bytes.Buffer, text []byte) { + raw := string(text) + lines := strings.Split(raw, linebreak) + for _, line := range lines { + trimmed := strings.Trim(line, " \n\t") + out.WriteString(trimmed) + out.WriteString(" ") + } +} + +// List renders the start and end of a list. +func (r *ASCIIRenderer) List(out *bytes.Buffer, text func() bool, flags int) { + r.listLevel++ + out.WriteString(linebreak) + text() + r.listLevel-- +} + +// ListItem renders list items and supports both ordered and unordered lists. +func (r *ASCIIRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) { + if flags&blackfriday.LIST_ITEM_BEGINNING_OF_LIST != 0 { + r.listItemCount = 1 + } else { + r.listItemCount++ + } + indent := strings.Repeat(r.Indentation, int(r.listLevel)) + var bullet string + if flags&blackfriday.LIST_TYPE_ORDERED != 0 { + bullet += fmt.Sprintf("%d.", r.listItemCount) + } else { + bullet += "*" + } + out.WriteString(indent + bullet + " ") + r.fw(out, text) + out.WriteString(linebreak) +} + +// Paragraph renders the start and end of a paragraph. +func (r *ASCIIRenderer) Paragraph(out *bytes.Buffer, text func() bool) { + out.WriteString(linebreak) + text() + out.WriteString(linebreak) +} + +// BlockCode renders a chunk of text that represents source code. +func (r *ASCIIRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string) { + out.WriteString(linebreak) + lines := []string{} + for _, line := range strings.Split(string(text), linebreak) { + indented := r.Indentation + line + lines = append(lines, indented) + } + out.WriteString(strings.Join(lines, linebreak)) +} + +func (r *ASCIIRenderer) GetFlags() int { return 0 } +func (r *ASCIIRenderer) HRule(out *bytes.Buffer) { + out.WriteString(linebreak + "----------" + linebreak) +} +func (r *ASCIIRenderer) LineBreak(out *bytes.Buffer) { out.WriteString(linebreak) } +func (r *ASCIIRenderer) TitleBlock(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) Header(out *bytes.Buffer, text func() bool, level int, id string) { text() } +func (r *ASCIIRenderer) BlockHtml(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) BlockQuote(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) TableRow(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) TableHeaderCell(out *bytes.Buffer, text []byte, align int) { r.fw(out, text) } +func (r *ASCIIRenderer) TableCell(out *bytes.Buffer, text []byte, align int) { r.fw(out, text) } +func (r *ASCIIRenderer) Footnotes(out *bytes.Buffer, text func() bool) { text() } +func (r *ASCIIRenderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) { r.fw(out, text) } +func (r *ASCIIRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) { r.fw(out, link) } +func (r *ASCIIRenderer) CodeSpan(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) DoubleEmphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) Emphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) RawHtmlTag(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) TripleEmphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) StrikeThrough(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) { r.fw(out, ref) } +func (r *ASCIIRenderer) Entity(out *bytes.Buffer, entity []byte) { r.fw(out, entity) } +func (r *ASCIIRenderer) Smartypants(out *bytes.Buffer, text []byte) { r.fw(out, text) } +func (r *ASCIIRenderer) DocumentHeader(out *bytes.Buffer) {} +func (r *ASCIIRenderer) DocumentFooter(out *bytes.Buffer) {} +func (r *ASCIIRenderer) TocHeaderWithAnchor(text []byte, level int, anchor string) {} +func (r *ASCIIRenderer) TocHeader(text []byte, level int) {} +func (r *ASCIIRenderer) TocFinalize() {} + +func (r *ASCIIRenderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) { + r.fw(out, header, body) +} + +func (r *ASCIIRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { + r.fw(out, link) +} + +func (r *ASCIIRenderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) { + r.fw(out, link) +} + +func (r *ASCIIRenderer) fw(out *bytes.Buffer, text ...[]byte) { + for _, t := range text { + out.Write(t) + } +} diff --git a/pkg/kubectl/cmd/templates/normalizers.go b/pkg/kubectl/cmd/templates/normalizers.go new file mode 100644 index 00000000000..eedd089c20f --- /dev/null +++ b/pkg/kubectl/cmd/templates/normalizers.go @@ -0,0 +1,91 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "strings" + + "github.com/MakeNowJust/heredoc" + "github.com/russross/blackfriday" + "github.com/spf13/cobra" +) + +const Indentation = ` ` + +// LongDesc normalizes a command's long description to follow the conventions. +func LongDesc(s string) string { + return normalizer{s}.heredoc().markdown().trim().string +} + +// Examples normalizes a command's examples to follow the conventions. +func Examples(s string) string { + return normalizer{s}.trim().indent().string +} + +// Normalize perform all required normalizations on a given command. +func Normalize(cmd *cobra.Command) *cobra.Command { + if len(cmd.Long) > 0 { + cmd.Long = LongDesc(cmd.Long) + } + if len(cmd.Example) > 0 { + cmd.Example = Examples(cmd.Example) + } + return cmd +} + +// NormalizeAll perform all required normalizations in the entire command tree. +func NormalizeAll(cmd *cobra.Command) *cobra.Command { + if cmd.HasSubCommands() { + for _, subCmd := range cmd.Commands() { + NormalizeAll(subCmd) + } + } + Normalize(cmd) + return cmd +} + +type normalizer struct { + string +} + +func (s normalizer) markdown() normalizer { + bytes := []byte(s.string) + formatted := blackfriday.Markdown(bytes, &ASCIIRenderer{Indentation: Indentation}, 0) + s.string = string(formatted) + return s +} + +func (s normalizer) heredoc() normalizer { + s.string = heredoc.Doc(s.string) + return s +} + +func (s normalizer) trim() normalizer { + s.string = strings.TrimSpace(s.string) + return s +} + +func (s normalizer) indent() normalizer { + indentedLines := []string{} + for _, line := range strings.Split(s.string, "\n") { + trimmed := strings.TrimSpace(line) + indented := Indentation + trimmed + indentedLines = append(indentedLines, indented) + } + s.string = strings.Join(indentedLines, "\n") + return s +} diff --git a/pkg/kubectl/cmd/templates/templates.go b/pkg/kubectl/cmd/templates/templates.go index d89e6007481..45a4aa3fd7a 100644 --- a/pkg/kubectl/cmd/templates/templates.go +++ b/pkg/kubectl/cmd/templates/templates.go @@ -62,7 +62,7 @@ Aliases: `{{if .HasExample}} Examples: -{{ indentLines (.Example | trimLeft) 2 }}{{end}}` + +{{ .Example}}{{end}}` + // SUBCOMMANDS `{{ if .HasAvailableSubCommands}} From f6d1ac72a05f5a82ef9194b3f2752ad8ac03157a Mon Sep 17 00:00:00 2001 From: Fabiano Franz Date: Fri, 7 Oct 2016 19:24:42 -0300 Subject: [PATCH 4/7] Use our own normalizers for cmd examples and descriptions --- pkg/kubectl/cmd/annotate.go | 44 ++++++++--------- pkg/kubectl/cmd/apply.go | 8 ++-- pkg/kubectl/cmd/attach.go | 4 +- pkg/kubectl/cmd/autoscale.go | 7 ++- pkg/kubectl/cmd/clusterinfo.go | 6 ++- pkg/kubectl/cmd/clusterinfo_dump.go | 35 +++++++------- pkg/kubectl/cmd/cmd.go | 56 +++++++++++----------- pkg/kubectl/cmd/completion.go | 27 +++++------ pkg/kubectl/cmd/config/config.go | 14 +++--- pkg/kubectl/cmd/config/create_authinfo.go | 26 +++++----- pkg/kubectl/cmd/config/create_cluster.go | 8 ++-- pkg/kubectl/cmd/config/create_context.go | 8 ++-- pkg/kubectl/cmd/config/current_context.go | 7 +-- pkg/kubectl/cmd/config/get_contexts.go | 15 +++--- pkg/kubectl/cmd/config/set.go | 6 ++- pkg/kubectl/cmd/config/unset.go | 5 +- pkg/kubectl/cmd/config/view.go | 7 +-- pkg/kubectl/cmd/convert.go | 13 ++--- pkg/kubectl/cmd/create.go | 7 +-- pkg/kubectl/cmd/create_configmap.go | 9 ++-- pkg/kubectl/cmd/create_deployment.go | 12 ++--- pkg/kubectl/cmd/create_namespace.go | 10 ++-- pkg/kubectl/cmd/create_quota.go | 12 ++--- pkg/kubectl/cmd/create_secret.go | 39 +++++++-------- pkg/kubectl/cmd/create_service.go | 36 +++++++------- pkg/kubectl/cmd/create_serviceaccount.go | 10 ++-- pkg/kubectl/cmd/delete.go | 7 +-- pkg/kubectl/cmd/describe.go | 10 ++-- pkg/kubectl/cmd/drain.go | 34 ++++++------- pkg/kubectl/cmd/edit.go | 18 +++---- pkg/kubectl/cmd/exec.go | 4 +- pkg/kubectl/cmd/explain.go | 14 +++--- pkg/kubectl/cmd/expose.go | 15 +++--- pkg/kubectl/cmd/get.go | 9 ++-- pkg/kubectl/cmd/help.go | 6 ++- pkg/kubectl/cmd/label.go | 13 ++--- pkg/kubectl/cmd/logs.go | 4 +- pkg/kubectl/cmd/patch.go | 6 +-- pkg/kubectl/cmd/portforward.go | 4 +- pkg/kubectl/cmd/proxy.go | 45 ++++++++--------- pkg/kubectl/cmd/replace.go | 10 ++-- pkg/kubectl/cmd/rollingupdate.go | 13 +++-- pkg/kubectl/cmd/rollout/rollout.go | 7 ++- pkg/kubectl/cmd/rollout/rollout_history.go | 7 +-- pkg/kubectl/cmd/rollout/rollout_pause.go | 6 +-- pkg/kubectl/cmd/rollout/rollout_resume.go | 6 +-- pkg/kubectl/cmd/rollout/rollout_status.go | 7 +-- pkg/kubectl/cmd/rollout/rollout_undo.go | 7 +-- pkg/kubectl/cmd/run.go | 8 ++-- pkg/kubectl/cmd/scale.go | 8 ++-- pkg/kubectl/cmd/set/set.go | 14 +++--- pkg/kubectl/cmd/set/set_image.go | 13 ++--- pkg/kubectl/cmd/set/set_resources.go | 51 ++++++++++---------- pkg/kubectl/cmd/stop.go | 7 +-- pkg/kubectl/cmd/taint.go | 17 +++---- pkg/kubectl/cmd/top.go | 4 +- pkg/kubectl/cmd/top_node.go | 6 +-- pkg/kubectl/cmd/top_pod.go | 22 ++++----- 58 files changed, 427 insertions(+), 406 deletions(-) diff --git a/pkg/kubectl/cmd/annotate.go b/pkg/kubectl/cmd/annotate.go index aa7fbb9bc31..6562ab5a6ee 100644 --- a/pkg/kubectl/cmd/annotate.go +++ b/pkg/kubectl/cmd/annotate.go @@ -23,11 +23,11 @@ import ( "io" "github.com/golang/glog" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -59,36 +59,36 @@ type AnnotateOptions struct { } var ( - annotate_long = dedent.Dedent(` + annotate_long = templates.LongDesc(` Update the annotations on one or more resources. - An annotation is a key/value pair that can hold larger (compared to a label), and possibly not human-readable, data. - It is intended to store non-identifying auxiliary data, especially data manipulated by tools and system extensions. - If --overwrite is true, then existing annotations can be overwritten, otherwise attempting to overwrite an annotation will result in an error. - If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used. + * An annotation is a key/value pair that can hold larger (compared to a label), and possibly not human-readable, data. + * It is intended to store non-identifying auxiliary data, especially data manipulated by tools and system extensions. + * If --overwrite is true, then existing annotations can be overwritten, otherwise attempting to overwrite an annotation will result in an error. + * If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used. - `) + valid_resources + ` + valid_resources) - annotate_example = dedent.Dedent(` - # Update pod 'foo' with the annotation 'description' and the value 'my frontend'. - # If the same annotation is set multiple times, only the last value will be applied - kubectl annotate pods foo description='my frontend' + annotate_example = templates.Examples(` + # Update pod 'foo' with the annotation 'description' and the value 'my frontend'. + # If the same annotation is set multiple times, only the last value will be applied + kubectl annotate pods foo description='my frontend' - # Update a pod identified by type and name in "pod.json" - kubectl annotate -f pod.json description='my frontend' + # Update a pod identified by type and name in "pod.json" + kubectl annotate -f pod.json description='my frontend' - # Update pod 'foo' with the annotation 'description' and the value 'my frontend running nginx', overwriting any existing value. - kubectl annotate --overwrite pods foo description='my frontend running nginx' + # Update pod 'foo' with the annotation 'description' and the value 'my frontend running nginx', overwriting any existing value. + kubectl annotate --overwrite pods foo description='my frontend running nginx' - # Update all pods in the namespace - kubectl annotate pods --all description='my frontend running nginx' + # Update all pods in the namespace + kubectl annotate pods --all description='my frontend running nginx' - # Update pod 'foo' only if the resource is unchanged from version 1. - kubectl annotate pods foo description='my frontend running nginx' --resource-version=1 + # Update pod 'foo' only if the resource is unchanged from version 1. + kubectl annotate pods foo description='my frontend running nginx' --resource-version=1 - # Update pod 'foo' by removing an annotation named 'description' if it exists. - # Does not require the --overwrite flag. - kubectl annotate pods foo description-`) + # Update pod 'foo' by removing an annotation named 'description' if it exists. + # Does not require the --overwrite flag. + kubectl annotate pods foo description-`) ) func NewCmdAnnotate(f cmdutil.Factory, out io.Writer) *cobra.Command { diff --git a/pkg/kubectl/cmd/apply.go b/pkg/kubectl/cmd/apply.go index a821f21201b..fee3696c3d4 100644 --- a/pkg/kubectl/cmd/apply.go +++ b/pkg/kubectl/cmd/apply.go @@ -22,7 +22,6 @@ import ( "time" "github.com/jonboulle/clockwork" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" @@ -32,6 +31,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/labels" @@ -58,16 +58,16 @@ const ( ) var ( - apply_long = dedent.Dedent(` + apply_long = templates.LongDesc(` Apply a configuration to a resource by filename or stdin. This resource will be created if it doesn't exist yet. To use 'apply', always create the resource initially with either 'apply' or 'create --save-config'. JSON and YAML formats are accepted. - + Alpha Disclaimer: the --prune functionality is not yet complete. Do not use unless you are aware of what the current state is. See https://issues.k8s.io/34274.`) - apply_example = dedent.Dedent(` + apply_example = templates.Examples(` # Apply the configuration in pod.json to a pod. kubectl apply -f ./pod.json diff --git a/pkg/kubectl/cmd/attach.go b/pkg/kubectl/cmd/attach.go index 33deb58db56..cdacd01fc3b 100644 --- a/pkg/kubectl/cmd/attach.go +++ b/pkg/kubectl/cmd/attach.go @@ -22,13 +22,13 @@ import ( "net/url" "github.com/golang/glog" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/remotecommand" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand" utilerrors "k8s.io/kubernetes/pkg/util/errors" @@ -36,7 +36,7 @@ import ( ) var ( - attach_example = dedent.Dedent(` + attach_example = templates.Examples(` # Get output from running pod 123456-7890, using the first container by default kubectl attach 123456-7890 diff --git a/pkg/kubectl/cmd/autoscale.go b/pkg/kubectl/cmd/autoscale.go index 8aa918d8fe0..2793e6eeaad 100644 --- a/pkg/kubectl/cmd/autoscale.go +++ b/pkg/kubectl/cmd/autoscale.go @@ -20,9 +20,8 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" - "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" utilerrors "k8s.io/kubernetes/pkg/util/errors" @@ -31,13 +30,13 @@ import ( ) var ( - autoscaleLong = dedent.Dedent(` + autoscaleLong = templates.LongDesc(` Creates an autoscaler that automatically chooses and sets the number of pods that run in a kubernetes cluster. Looks up a Deployment, ReplicaSet, or ReplicationController by name and creates an autoscaler that uses the given resource as a reference. An autoscaler can automatically increase or decrease number of pods deployed within the system as needed.`) - autoscaleExample = dedent.Dedent(` + autoscaleExample = templates.Examples(` # Auto scale a deployment "foo", with the number of pods between 2 and 10, target CPU utilization specified so a default autoscaling policy will be used: kubectl autoscale deployment foo --min=2 --max=10 diff --git a/pkg/kubectl/cmd/clusterinfo.go b/pkg/kubectl/cmd/clusterinfo.go index 76eb016a69b..2bdc8a06f8c 100644 --- a/pkg/kubectl/cmd/clusterinfo.go +++ b/pkg/kubectl/cmd/clusterinfo.go @@ -23,6 +23,7 @@ import ( "strconv" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -30,8 +31,9 @@ import ( "github.com/spf13/cobra" ) -var longDescr = `Display addresses of the master and services with label kubernetes.io/cluster-service=true -To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.` +var longDescr = templates.LongDesc(` + Display addresses of the master and services with label kubernetes.io/cluster-service=true + To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.`) func NewCmdClusterInfo(f cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ diff --git a/pkg/kubectl/cmd/clusterinfo_dump.go b/pkg/kubectl/cmd/clusterinfo_dump.go index e51f305d4ea..61824ff2264 100644 --- a/pkg/kubectl/cmd/clusterinfo_dump.go +++ b/pkg/kubectl/cmd/clusterinfo_dump.go @@ -26,6 +26,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) @@ -46,28 +47,28 @@ func NewCmdClusterInfoDump(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command { return cmd } -const ( - dumpLong = ` -Dumps cluster info out suitable for debugging and diagnosing cluster problems. By default, dumps everything to -stdout. You can optionally specify a directory with --output-directory. If you specify a directory, kubernetes will -build a set of files in that directory. By default only dumps things in the 'kube-system' namespace, but you can -switch to a different namespace with the --namespaces flag, or specify --all-namespaces to dump all namespaces. +var ( + dumpLong = templates.LongDesc(` + Dumps cluster info out suitable for debugging and diagnosing cluster problems. By default, dumps everything to + stdout. You can optionally specify a directory with --output-directory. If you specify a directory, kubernetes will + build a set of files in that directory. By default only dumps things in the 'kube-system' namespace, but you can + switch to a different namespace with the --namespaces flag, or specify --all-namespaces to dump all namespaces. -The command also dumps the logs of all of the pods in the cluster, these logs are dumped into different directories -based on namespace and pod name. -` + The command also dumps the logs of all of the pods in the cluster, these logs are dumped into different directories + based on namespace and pod name.`) - dumpExample = `# Dump current cluster state to stdout -kubectl cluster-info dump + dumpExample = templates.Examples(` + # Dump current cluster state to stdout + kubectl cluster-info dump -# Dump current cluster state to /path/to/cluster-state -kubectl cluster-info dump --output-directory=/path/to/cluster-state + # Dump current cluster state to /path/to/cluster-state + kubectl cluster-info dump --output-directory=/path/to/cluster-state -# Dump all namespaces to stdout -kubectl cluster-info dump --all-namespaces + # Dump all namespaces to stdout + kubectl cluster-info dump --all-namespaces -# Dump a set of namespaces to /path/to/cluster-state -kubectl cluster-info dump --namespaces default,kube-system --output-directory=/path/to/cluster-state` + # Dump a set of namespaces to /path/to/cluster-state + kubectl cluster-info dump --namespaces default,kube-system --output-directory=/path/to/cluster-state`) ) func setupOutputWriter(cmd *cobra.Command, defaultWriter io.Writer, filename string) io.Writer { diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 83979c90a02..3c70434df13 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -169,31 +169,32 @@ __custom_func() { // and add a short forms entry in expandResourceShortcut() when appropriate. // TODO: This should be populated using the discovery information from apiserver. valid_resources = `Valid resource types include: - * clusters (valid only for federation apiservers) - * componentstatuses (aka 'cs') - * configmaps (aka 'cm') - * daemonsets (aka 'ds') - * deployments (aka 'deploy') - * events (aka 'ev') - * endpoints (aka 'ep') - * horizontalpodautoscalers (aka 'hpa') - * ingress (aka 'ing') - * jobs - * limitranges (aka 'limits') - * nodes (aka 'no') - * namespaces (aka 'ns') - * petsets (alpha feature, may be unstable) - * pods (aka 'po') - * persistentvolumes (aka 'pv') - * persistentvolumeclaims (aka 'pvc') - * quota - * resourcequotas (aka 'quota') - * replicasets (aka 'rs') - * replicationcontrollers (aka 'rc') - * secrets - * serviceaccounts (aka 'sa') - * services (aka 'svc') -` + + * clusters (valid only for federation apiservers) + * componentstatuses (aka 'cs') + * configmaps (aka 'cm') + * daemonsets (aka 'ds') + * deployments (aka 'deploy') + * events (aka 'ev') + * endpoints (aka 'ep') + * horizontalpodautoscalers (aka 'hpa') + * ingress (aka 'ing') + * jobs + * limitranges (aka 'limits') + * nodes (aka 'no') + * namespaces (aka 'ns') + * petsets (alpha feature, may be unstable) + * pods (aka 'po') + * persistentvolumes (aka 'pv') + * persistentvolumeclaims (aka 'pvc') + * quota + * resourcequotas (aka 'quota') + * replicasets (aka 'rs') + * replicationcontrollers (aka 'rc') + * secrets + * serviceaccounts (aka 'sa') + * services (aka 'svc') + ` usage_template = `{{if gt .Aliases 0}} Aliases: @@ -229,9 +230,10 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob cmds := &cobra.Command{ Use: "kubectl", Short: "kubectl controls the Kubernetes cluster manager", - Long: `kubectl controls the Kubernetes cluster manager. + Long: templates.LongDesc(` + kubectl controls the Kubernetes cluster manager. -Find more information at https://github.com/kubernetes/kubernetes.`, + Find more information at https://github.com/kubernetes/kubernetes.`), Run: runHelp, BashCompletionFunction: bash_completion_func, } diff --git a/pkg/kubectl/cmd/completion.go b/pkg/kubectl/cmd/completion.go index b83889fb7f1..0bfb6818370 100644 --- a/pkg/kubectl/cmd/completion.go +++ b/pkg/kubectl/cmd/completion.go @@ -20,36 +20,34 @@ import ( "bytes" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) var ( - completion_long = dedent.Dedent(` + completion_long = templates.LongDesc(` Output shell completion code for the given shell (bash or zsh). This command prints shell code which must be evaluation to provide interactive completion of kubectl commands. - `) - completion_example = dedent.Dedent(` - $ source <(kubectl completion bash) + $ source <(kubectl completion bash) will load the kubectl completion code for bash. Note that this depends on the bash-completion framework. It must be sourced before sourcing the kubectl completion, e.g. on the Mac: - $ brew install bash-completion - $ source $(brew --prefix)/etc/bash_completion - $ source <(kubectl completion bash) + $ brew install bash-completion + $ source $(brew --prefix)/etc/bash_completion + $ source <(kubectl completion bash) - If you use zsh*, the following will load kubectl zsh completion: + If you use zsh[1], the following will load kubectl zsh completion: - $ source <(kubectl completion zsh) + $ source <(kubectl completion zsh) - * zsh completions are only supported in versions of zsh >= 5.2`) + [1] zsh completions are only supported in versions of zsh >= 5.2`) ) var ( @@ -66,10 +64,9 @@ func NewCmdCompletion(f cmdutil.Factory, out io.Writer) *cobra.Command { } cmd := &cobra.Command{ - Use: "completion SHELL", - Short: "Output shell completion code for the given shell (bash or zsh)", - Long: completion_long, - Example: completion_example, + Use: "completion SHELL", + Short: "Output shell completion code for the given shell (bash or zsh)", + Long: completion_long, Run: func(cmd *cobra.Command, args []string) { err := RunCompletion(f, out, cmd, args) cmdutil.CheckErr(err) diff --git a/pkg/kubectl/cmd/config/config.go b/pkg/kubectl/cmd/config/config.go index 1354c9db306..76860a3d37c 100644 --- a/pkg/kubectl/cmd/config/config.go +++ b/pkg/kubectl/cmd/config/config.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" ) // NewCmdConfig creates a command object for the "config" action, and adds all child commands to it. @@ -35,13 +36,14 @@ func NewCmdConfig(pathOptions *clientcmd.PathOptions, out io.Writer) *cobra.Comm cmd := &cobra.Command{ Use: "config SUBCOMMAND", Short: "Modify kubeconfig files", - Long: `Modify kubeconfig files using subcommands like "kubectl config set current-context my-context" + Long: templates.LongDesc(` + Modify kubeconfig files using subcommands like "kubectl config set current-context my-context" -The loading order follows these rules: -1. If the --` + pathOptions.ExplicitFileFlag + ` flag is set, then only that file is loaded. The flag may only be set once and no merging takes place. -2. If $` + pathOptions.EnvVar + ` environment variable is set, then it is used a list of paths (normal path delimitting rules for your system). These paths are merged. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. If no files in the chain exist, then it creates the last file in the list. -3. Otherwise, ` + path.Join("${HOME}", pathOptions.GlobalFileSubpath) + ` is used and no merging takes place. -`, + The loading order follows these rules: + + 1. If the --` + pathOptions.ExplicitFileFlag + ` flag is set, then only that file is loaded. The flag may only be set once and no merging takes place. + 2. If $` + pathOptions.EnvVar + ` environment variable is set, then it is used a list of paths (normal path delimitting rules for your system). These paths are merged. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. If no files in the chain exist, then it creates the last file in the list. + 3. Otherwise, ` + path.Join("${HOME}", pathOptions.GlobalFileSubpath) + ` is used and no merging takes place.`), Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/pkg/kubectl/cmd/config/create_authinfo.go b/pkg/kubectl/cmd/config/create_authinfo.go index d7ed513fe6d..8c2b2107bd0 100644 --- a/pkg/kubectl/cmd/config/create_authinfo.go +++ b/pkg/kubectl/cmd/config/create_authinfo.go @@ -24,11 +24,11 @@ import ( "path/filepath" "strings" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/util/flag" ) @@ -55,23 +55,23 @@ const ( ) var ( - create_authinfo_long = fmt.Sprintf(` -Sets a user entry in kubeconfig -Specifying a name that already exists will merge new fields on top of existing values. + create_authinfo_long = fmt.Sprintf(templates.LongDesc(` + Sets a user entry in kubeconfig - Client-certificate flags: - --%v=certfile --%v=keyfile + Specifying a name that already exists will merge new fields on top of existing values. - Bearer token flags: - --%v=bearer_token + Client-certificate flags: + --%v=certfile --%v=keyfile - Basic auth flags: - --%v=basic_user --%v=basic_password + Bearer token flags: + --%v=bearer_token - Bearer token and basic auth are mutually exclusive. -`, clientcmd.FlagCertFile, clientcmd.FlagKeyFile, clientcmd.FlagBearerToken, clientcmd.FlagUsername, clientcmd.FlagPassword) + Basic auth flags: + --%v=basic_user --%v=basic_password - create_authinfo_example = dedent.Dedent(` + Bearer token and basic auth are mutually exclusive.`), clientcmd.FlagCertFile, clientcmd.FlagKeyFile, clientcmd.FlagBearerToken, clientcmd.FlagUsername, clientcmd.FlagPassword) + + create_authinfo_example = templates.Examples(` # Set only the "client-key" field on the "cluster-admin" # entry, without touching other values: kubectl config set-credentials cluster-admin --client-key=~/.kube/admin.key diff --git a/pkg/kubectl/cmd/config/create_cluster.go b/pkg/kubectl/cmd/config/create_cluster.go index 27c31c08de9..e5a30f1362d 100644 --- a/pkg/kubectl/cmd/config/create_cluster.go +++ b/pkg/kubectl/cmd/config/create_cluster.go @@ -23,11 +23,11 @@ import ( "io/ioutil" "path/filepath" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/util/flag" ) @@ -42,10 +42,12 @@ type createClusterOptions struct { } var ( - create_cluster_long = dedent.Dedent(` + create_cluster_long = templates.LongDesc(` Sets a cluster entry in kubeconfig. + Specifying a name that already exists will merge new fields on top of existing values for those fields.`) - create_cluster_example = dedent.Dedent(` + + create_cluster_example = templates.Examples(` # Set only the server field on the e2e cluster entry without touching other values. kubectl config set-cluster e2e --server=https://1.2.3.4 diff --git a/pkg/kubectl/cmd/config/create_context.go b/pkg/kubectl/cmd/config/create_context.go index 7555b7a4430..9e3c1dc2e23 100644 --- a/pkg/kubectl/cmd/config/create_context.go +++ b/pkg/kubectl/cmd/config/create_context.go @@ -21,11 +21,11 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/util/flag" ) @@ -38,10 +38,12 @@ type createContextOptions struct { } var ( - create_context_long = dedent.Dedent(` + create_context_long = templates.LongDesc(` Sets a context entry in kubeconfig + Specifying a name that already exists will merge new fields on top of existing values for those fields.`) - create_context_example = dedent.Dedent(` + + create_context_example = templates.Examples(` # Set the user field on the gce context entry without touching other values kubectl config set-context gce --user=cluster-admin`) ) diff --git a/pkg/kubectl/cmd/config/current_context.go b/pkg/kubectl/cmd/config/current_context.go index fc23cd705c0..7a576590b7a 100644 --- a/pkg/kubectl/cmd/config/current_context.go +++ b/pkg/kubectl/cmd/config/current_context.go @@ -20,10 +20,10 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) @@ -32,9 +32,10 @@ type CurrentContextOptions struct { } var ( - current_context_long = dedent.Dedent(` + current_context_long = templates.LongDesc(` Displays the current-context`) - current_context_example = dedent.Dedent(` + + current_context_example = templates.Examples(` # Display the current-context kubectl config current-context`) ) diff --git a/pkg/kubectl/cmd/config/get_contexts.go b/pkg/kubectl/cmd/config/get_contexts.go index 64d194a0307..5ad1d92905a 100644 --- a/pkg/kubectl/cmd/config/get_contexts.go +++ b/pkg/kubectl/cmd/config/get_contexts.go @@ -26,7 +26,7 @@ import ( "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" "k8s.io/kubernetes/pkg/kubectl" - + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" utilerrors "k8s.io/kubernetes/pkg/util/errors" "k8s.io/kubernetes/pkg/util/sets" @@ -41,14 +41,15 @@ type GetContextsOptions struct { out io.Writer } -const ( - getContextsLong = `Displays one or many contexts from the kubeconfig file.` +var ( + getContextsLong = templates.LongDesc(`Displays one or many contexts from the kubeconfig file.`) - getContextsExample = `# List all the contexts in your kubeconfig file -kubectl config get-contexts + getContextsExample = templates.Examples(` + # List all the contexts in your kubeconfig file + kubectl config get-contexts -# Describe one context in your kubeconfig file. -kubectl config get-contexts my-context` + # Describe one context in your kubeconfig file. + kubectl config get-contexts my-context`) ) // NewCmdConfigGetContexts creates a command object for the "get-contexts" action, which diff --git a/pkg/kubectl/cmd/config/set.go b/pkg/kubectl/cmd/config/set.go index b872d9b76fb..233092bf542 100644 --- a/pkg/kubectl/cmd/config/set.go +++ b/pkg/kubectl/cmd/config/set.go @@ -24,10 +24,10 @@ import ( "reflect" "strings" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/util/flag" ) @@ -43,9 +43,11 @@ type setOptions struct { setRawBytes flag.Tristate } -var set_long = dedent.Dedent(` +var set_long = templates.LongDesc(` Sets an individual value in a kubeconfig file + PROPERTY_NAME is a dot delimited name where each token represents either an attribute name or a map key. Map keys may not contain dots. + PROPERTY_VALUE is the new value you wish to set. Binary fields such as 'certificate-authority-data' expect a base64 encoded string unless the --set-raw-bytes flag is used.`) func NewCmdConfigSet(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command { diff --git a/pkg/kubectl/cmd/config/unset.go b/pkg/kubectl/cmd/config/unset.go index 1af0ff9ebbc..9b685f638d7 100644 --- a/pkg/kubectl/cmd/config/unset.go +++ b/pkg/kubectl/cmd/config/unset.go @@ -22,8 +22,8 @@ import ( "io" "reflect" - "github.com/renstrom/dedent" "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" ) @@ -33,8 +33,9 @@ type unsetOptions struct { propertyName string } -var unset_long = dedent.Dedent(` +var unset_long = templates.LongDesc(` Unsets an individual value in a kubeconfig file + PROPERTY_NAME is a dot delimited name where each token represents either an attribute name or a map key. Map keys may not contain dots.`) func NewCmdConfigUnset(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command { diff --git a/pkg/kubectl/cmd/config/view.go b/pkg/kubectl/cmd/config/view.go index 98d2441dc19..5193c80c4ef 100644 --- a/pkg/kubectl/cmd/config/view.go +++ b/pkg/kubectl/cmd/config/view.go @@ -21,13 +21,13 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/util/flag" ) @@ -41,11 +41,12 @@ type ViewOptions struct { } var ( - view_long = dedent.Dedent(` + view_long = templates.LongDesc(` Display merged kubeconfig settings or a specified kubeconfig file. You can use --output jsonpath={...} to extract specific values using a jsonpath expression.`) - view_example = dedent.Dedent(` + + view_example = templates.Examples(` # Show Merged kubeconfig settings. kubectl config view diff --git a/pkg/kubectl/cmd/convert.go b/pkg/kubectl/cmd/convert.go index 1f67f483ae1..cdf5c9fca00 100644 --- a/pkg/kubectl/cmd/convert.go +++ b/pkg/kubectl/cmd/convert.go @@ -21,12 +21,11 @@ import ( "io" "os" - "github.com/renstrom/dedent" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -35,7 +34,7 @@ import ( ) var ( - convert_long = dedent.Dedent(` + convert_long = templates.LongDesc(` Convert config files between different API versions. Both YAML and JSON formats are accepted. @@ -44,10 +43,9 @@ var ( not supported, convert to latest version. The default output will be printed to stdout in YAML format. One can use -o option - to change to output destination. - `) + to change to output destination.`) - convert_example = dedent.Dedent(` + convert_example = templates.Examples(` # Convert 'pod.yaml' to latest version and print to stdout. kubectl convert -f pod.yaml @@ -56,8 +54,7 @@ var ( kubectl convert -f pod.yaml --local -o json # Convert all files under current directory to latest version and create them all. - kubectl convert -f . | kubectl create -f - - `) + kubectl convert -f . | kubectl create -f -`) ) // NewCmdConvert creates a command object for the generic "convert" action, which diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go index cada40747b3..c82e9b3d4f9 100644 --- a/pkg/kubectl/cmd/create.go +++ b/pkg/kubectl/cmd/create.go @@ -20,22 +20,23 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" ) var ( - create_long = dedent.Dedent(` + create_long = templates.LongDesc(` Create a resource by filename or stdin. JSON and YAML formats are accepted.`) - create_example = dedent.Dedent(` + + create_example = templates.Examples(` # Create a pod using the data in pod.json. kubectl create -f ./pod.json diff --git a/pkg/kubectl/cmd/create_configmap.go b/pkg/kubectl/cmd/create_configmap.go index 04b14acc0be..07d08309989 100644 --- a/pkg/kubectl/cmd/create_configmap.go +++ b/pkg/kubectl/cmd/create_configmap.go @@ -20,15 +20,15 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) var ( - configMapLong = dedent.Dedent(` + configMapLong = templates.LongDesc(` Create a configmap based on a file, directory, or specified literal value. A single configmap may package one or more key/value pairs. @@ -38,10 +38,9 @@ var ( When creating a configmap based on a directory, each file whose basename is a valid key in the directory will be packaged into the configmap. Any directory entries except regular files are ignored (e.g. subdirectories, - symlinks, devices, pipes, etc). - `) + symlinks, devices, pipes, etc).`) - configMapExample = dedent.Dedent(` + configMapExample = templates.Examples(` # Create a new configmap named my-config with keys for each file in folder bar kubectl create configmap my-config --from-file=path/to/bar diff --git a/pkg/kubectl/cmd/create_deployment.go b/pkg/kubectl/cmd/create_deployment.go index 2f98cbe5d79..000e5111b5e 100644 --- a/pkg/kubectl/cmd/create_deployment.go +++ b/pkg/kubectl/cmd/create_deployment.go @@ -20,20 +20,20 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) var ( - deploymentLong = dedent.Dedent(` - Create a deployment with the specified name.`) + deploymentLong = templates.LongDesc(` + Create a deployment with the specified name.`) - deploymentExample = dedent.Dedent(` - # Create a new deployment named my-dep that runs the busybox image. - kubectl create deployment my-dep --image=busybox`) + deploymentExample = templates.Examples(` + # Create a new deployment named my-dep that runs the busybox image. + kubectl create deployment my-dep --image=busybox`) ) // NewCmdCreateDeployment is a macro command to create a new deployment diff --git a/pkg/kubectl/cmd/create_namespace.go b/pkg/kubectl/cmd/create_namespace.go index 7ab05b3c170..9a419f71eb1 100644 --- a/pkg/kubectl/cmd/create_namespace.go +++ b/pkg/kubectl/cmd/create_namespace.go @@ -20,20 +20,20 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) var ( - namespaceLong = dedent.Dedent(` + namespaceLong = templates.LongDesc(` Create a namespace with the specified name.`) - namespaceExample = dedent.Dedent(` - # Create a new namespace named my-namespace - kubectl create namespace my-namespace`) + namespaceExample = templates.Examples(` + # Create a new namespace named my-namespace + kubectl create namespace my-namespace`) ) // NewCmdCreateNamespace is a macro command to create a new namespace diff --git a/pkg/kubectl/cmd/create_quota.go b/pkg/kubectl/cmd/create_quota.go index dd661c1b630..1a23dba35b2 100644 --- a/pkg/kubectl/cmd/create_quota.go +++ b/pkg/kubectl/cmd/create_quota.go @@ -20,22 +20,22 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) var ( - quotaLong = dedent.Dedent(` + quotaLong = templates.LongDesc(` Create a resourcequota with the specified name, hard limits and optional scopes`) - quotaExample = dedent.Dedent(` - // Create a new resourcequota named my-quota + quotaExample = templates.Examples(` + # Create a new resourcequota named my-quota $ kubectl create quota my-quota --hard=cpu=1,memory=1G,pods=2,services=3,replicationcontrollers=2,resourcequotas=1,secrets=5,persistentvolumeclaims=10 - - // Create a new resourcequota named best-effort + + # Create a new resourcequota named best-effort $ kubectl create quota best-effort --hard=pods=100 --scopes=BestEffort`) ) diff --git a/pkg/kubectl/cmd/create_secret.go b/pkg/kubectl/cmd/create_secret.go index 6c12e36dbea..8495f4db79b 100644 --- a/pkg/kubectl/cmd/create_secret.go +++ b/pkg/kubectl/cmd/create_secret.go @@ -20,10 +20,10 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) @@ -45,7 +45,7 @@ func NewCmdCreateSecret(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command { } var ( - secretLong = dedent.Dedent(` + secretLong = templates.LongDesc(` Create a secret based on a file, directory, or specified literal value. A single secret may package one or more key/value pairs. @@ -55,18 +55,17 @@ var ( When creating a secret based on a directory, each file whose basename is a valid key in the directory will be packaged into the secret. Any directory entries except regular files are ignored (e.g. subdirectories, - symlinks, devices, pipes, etc). - `) + symlinks, devices, pipes, etc).`) - secretExample = dedent.Dedent(` - # Create a new secret named my-secret with keys for each file in folder bar - kubectl create secret generic my-secret --from-file=path/to/bar + secretExample = templates.Examples(` + # Create a new secret named my-secret with keys for each file in folder bar + kubectl create secret generic my-secret --from-file=path/to/bar - # Create a new secret named my-secret with specified keys instead of names on disk - kubectl create secret generic my-secret --from-file=ssh-privatekey=~/.ssh/id_rsa --from-file=ssh-publickey=~/.ssh/id_rsa.pub + # Create a new secret named my-secret with specified keys instead of names on disk + kubectl create secret generic my-secret --from-file=ssh-privatekey=~/.ssh/id_rsa --from-file=ssh-publickey=~/.ssh/id_rsa.pub - # Create a new secret named my-secret with key1=supersecret and key2=topsecret - kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret`) + # Create a new secret named my-secret with key1=supersecret and key2=topsecret + kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret`) ) // NewCmdCreateSecretGeneric is a command to create generic secrets from files, directories, or literal values @@ -118,21 +117,23 @@ func CreateSecretGeneric(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command } var ( - secretForDockerRegistryLong = dedent.Dedent(` + secretForDockerRegistryLong = templates.LongDesc(` Create a new secret for use with Docker registries. Dockercfg secrets are used to authenticate against Docker registries. When using the Docker command line to push images, you can authenticate to a given registry by running - 'docker login DOCKER_REGISTRY_SERVER --username=DOCKER_USER --password=DOCKER_PASSWORD --email=DOCKER_EMAIL'. - That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to + + $ docker login DOCKER_REGISTRY_SERVER --username=DOCKER_USER --password=DOCKER_PASSWORD --email=DOCKER_EMAIL'. + + That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to authenticate to the registry. When creating applications, you may have a Docker registry that requires authentication. In order for the nodes to pull images on your behalf, they have to have the credentials. You can provide this information by creating a dockercfg secret and attaching it to your service account.`) - secretForDockerRegistryExample = dedent.Dedent(` + secretForDockerRegistryExample = templates.Examples(` # If you don't already have a .dockercfg file, you can create a dockercfg secret directly by using: kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL`) ) @@ -198,14 +199,14 @@ func CreateSecretDockerRegistry(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra. } var ( - secretForTLSLong = dedent.Dedent(` + secretForTLSLong = templates.LongDesc(` Create a TLS secret from the given public/private key pair. The public/private key pair must exist before hand. The public key certificate must be .PEM encoded and match the given private key.`) - secretForTLSExample = dedent.Dedent(` - # Create a new TLS secret named tls-secret with the given key pair: - kubectl create secret tls tls-secret --cert=path/to/tls.cert --key=path/to/tls.key`) + secretForTLSExample = templates.Examples(` + # Create a new TLS secret named tls-secret with the given key pair: + kubectl create secret tls tls-secret --cert=path/to/tls.cert --key=path/to/tls.key`) ) // NewCmdCreateSecretTLS is a macro command for creating secrets to work with Docker registries diff --git a/pkg/kubectl/cmd/create_service.go b/pkg/kubectl/cmd/create_service.go index a103193fe88..1b2fa9a680e 100644 --- a/pkg/kubectl/cmd/create_service.go +++ b/pkg/kubectl/cmd/create_service.go @@ -20,11 +20,11 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) @@ -47,15 +47,15 @@ func NewCmdCreateService(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command { } var ( - serviceClusterIPLong = dedent.Dedent(` - Create a clusterIP service with the specified name.`) + serviceClusterIPLong = templates.LongDesc(` + Create a clusterIP service with the specified name.`) - serviceClusterIPExample = dedent.Dedent(` - # Create a new clusterIP service named my-cs - kubectl create service clusterip my-cs --tcp=5678:8080 + serviceClusterIPExample = templates.Examples(` + # Create a new clusterIP service named my-cs + kubectl create service clusterip my-cs --tcp=5678:8080 - # Create a new clusterIP service named my-cs (in headless mode) - kubectl create service clusterip my-cs --clusterip="None"`) + # Create a new clusterIP service named my-cs (in headless mode) + kubectl create service clusterip my-cs --clusterip="None"`) ) func addPortFlags(cmd *cobra.Command) { @@ -110,12 +110,12 @@ func CreateServiceClusterIP(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Comm } var ( - serviceNodePortLong = dedent.Dedent(` - Create a nodeport service with the specified name.`) + serviceNodePortLong = templates.LongDesc(` + Create a nodeport service with the specified name.`) - serviceNodePortExample = dedent.Dedent(` - # Create a new nodeport service named my-ns - kubectl create service nodeport my-ns --tcp=5678:8080`) + serviceNodePortExample = templates.Examples(` + # Create a new nodeport service named my-ns + kubectl create service nodeport my-ns --tcp=5678:8080`) ) // NewCmdCreateServiceNodePort is a macro command for creating a NodePort service @@ -167,12 +167,12 @@ func CreateServiceNodePort(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Comma } var ( - serviceLoadBalancerLong = dedent.Dedent(` - Create a LoadBalancer service with the specified name.`) + serviceLoadBalancerLong = templates.LongDesc(` + Create a LoadBalancer service with the specified name.`) - serviceLoadBalancerExample = dedent.Dedent(` - # Create a new nodeport service named my-lbs - kubectl create service loadbalancer my-lbs --tcp=5678:8080`) + serviceLoadBalancerExample = templates.Examples(` + # Create a new nodeport service named my-lbs + kubectl create service loadbalancer my-lbs --tcp=5678:8080`) ) // NewCmdCreateServiceLoadBalancer is a macro command for creating a LoadBalancer service diff --git a/pkg/kubectl/cmd/create_serviceaccount.go b/pkg/kubectl/cmd/create_serviceaccount.go index 4b7a520d4a2..bad69ff96f3 100644 --- a/pkg/kubectl/cmd/create_serviceaccount.go +++ b/pkg/kubectl/cmd/create_serviceaccount.go @@ -20,20 +20,20 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) var ( - serviceAccountLong = dedent.Dedent(` + serviceAccountLong = templates.LongDesc(` Create a service account with the specified name.`) - serviceAccountExample = dedent.Dedent(` - # Create a new service account named my-service-account - $ kubectl create serviceaccount my-service-account`) + serviceAccountExample = templates.Examples(` + # Create a new service account named my-service-account + $ kubectl create serviceaccount my-service-account`) ) // NewCmdCreateServiceAccount is a macro command to create a new service account diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index 50f9f4b7db6..1bcc01e7d58 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -21,19 +21,19 @@ import ( "io" "time" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" ) var ( - delete_long = dedent.Dedent(` + delete_long = templates.LongDesc(` Delete resources by filenames, stdin, resources and names, or by resources and label selector. JSON and YAML formats are accepted. @@ -43,7 +43,8 @@ var ( Note that the delete command does NOT do resource version checks, so if someone submits an update to a resource right when you submit a delete, their update will be lost along with the rest of the resource.`) - delete_example = dedent.Dedent(` + + delete_example = templates.Examples(` # Delete a pod using the type and name specified in pod.json. kubectl delete -f ./pod.json diff --git a/pkg/kubectl/cmd/describe.go b/pkg/kubectl/cmd/describe.go index b3b6bc53369..ee464c65ada 100644 --- a/pkg/kubectl/cmd/describe.go +++ b/pkg/kubectl/cmd/describe.go @@ -21,12 +21,12 @@ import ( "io" "strings" - "github.com/renstrom/dedent" "github.com/spf13/cobra" apierrors "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -34,19 +34,19 @@ import ( ) var ( - describe_long = dedent.Dedent(` + describe_long = templates.LongDesc(` Show details of a specific resource or group of resources. This command joins many API calls together to form a detailed description of a given resource or group of resources. - $ kubectl describe TYPE NAME_PREFIX + $ kubectl describe TYPE NAME_PREFIX will first check for an exact match on TYPE and NAME_PREFIX. If no such resource exists, it will output details for every resource that has a name prefixed with NAME_PREFIX. - `) + valid_resources + ` + valid_resources) - describe_example = dedent.Dedent(` + describe_example = templates.Examples(` # Describe a node kubectl describe nodes kubernetes-node-emt8.c.myproject.internal diff --git a/pkg/kubectl/cmd/drain.go b/pkg/kubectl/cmd/drain.go index 1a0ea7df8c9..6a2e2e36073 100644 --- a/pkg/kubectl/cmd/drain.go +++ b/pkg/kubectl/cmd/drain.go @@ -23,7 +23,6 @@ import ( "reflect" "strings" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" @@ -31,6 +30,7 @@ import ( "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubelet/types" @@ -71,13 +71,12 @@ const ( ) var ( - cordon_long = dedent.Dedent(` - Mark node as unschedulable. - `) - cordon_example = dedent.Dedent(` + cordon_long = templates.LongDesc(` + Mark node as unschedulable.`) + + cordon_example = templates.Examples(` # Mark node "foo" as unschedulable. - kubectl cordon foo - `) + kubectl cordon foo`) ) func NewCmdCordon(f cmdutil.Factory, out io.Writer) *cobra.Command { @@ -97,13 +96,12 @@ func NewCmdCordon(f cmdutil.Factory, out io.Writer) *cobra.Command { } var ( - uncordon_long = dedent.Dedent(` - Mark node as schedulable. - `) - uncordon_example = dedent.Dedent(` + uncordon_long = templates.LongDesc(` + Mark node as schedulable.`) + + uncordon_example = templates.Examples(` # Mark node "foo" as schedulable. - $ kubectl uncordon foo - `) + $ kubectl uncordon foo`) ) func NewCmdUncordon(f cmdutil.Factory, out io.Writer) *cobra.Command { @@ -123,7 +121,7 @@ func NewCmdUncordon(f cmdutil.Factory, out io.Writer) *cobra.Command { } var ( - drain_long = dedent.Dedent(` + drain_long = templates.LongDesc(` Drain node in preparation for maintenance. The given node will be marked unschedulable to prevent new pods from arriving. @@ -139,16 +137,14 @@ var ( When you are ready to put the node back into service, use kubectl uncordon, which will make the node schedulable again. - ![Workflow](http://kubernetes.io/images/docs/kubectl_drain.svg) - `) + ![Workflow](http://kubernetes.io/images/docs/kubectl_drain.svg)`) - drain_example = dedent.Dedent(` + drain_example = templates.Examples(` # Drain node "foo", even if there are pods not managed by a ReplicationController, ReplicaSet, Job, or DaemonSet on it. $ kubectl drain foo --force # As above, but abort if there are pods not managed by a ReplicationController, ReplicaSet, Job, or DaemonSet, and use a grace period of 15 minutes. - $ kubectl drain foo --grace-period=900 - `) + $ kubectl drain foo --grace-period=900`) ) func NewCmdDrain(f cmdutil.Factory, out io.Writer) *cobra.Command { diff --git a/pkg/kubectl/cmd/edit.go b/pkg/kubectl/cmd/edit.go index 8d77ab87d88..68090f6702a 100644 --- a/pkg/kubectl/cmd/edit.go +++ b/pkg/kubectl/cmd/edit.go @@ -27,12 +27,12 @@ import ( gruntime "runtime" "strings" - "github.com/renstrom/dedent" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/cmd/util/editor" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -47,7 +47,7 @@ import ( ) var ( - editLong = dedent.Dedent(` + editLong = templates.LongDesc(` Edit a resource from the default editor. The edit command allows you to directly edit any API resource you can retrieve via the @@ -68,15 +68,15 @@ var ( to apply your changes to the newer version of the resource, or update your temporary saved copy to include the latest resource version.`) - editExample = dedent.Dedent(` - # Edit the service named 'docker-registry': - kubectl edit svc/docker-registry + editExample = templates.Examples(` + # Edit the service named 'docker-registry': + kubectl edit svc/docker-registry - # Use an alternative editor - KUBE_EDITOR="nano" kubectl edit svc/docker-registry + # Use an alternative editor + KUBE_EDITOR="nano" kubectl edit svc/docker-registry - # Edit the service 'docker-registry' in JSON using the v1 API format: - kubectl edit svc/docker-registry --output-version=v1 -o json`) + # Edit the service 'docker-registry' in JSON using the v1 API format: + kubectl edit svc/docker-registry --output-version=v1 -o json`) ) func NewCmdEdit(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { diff --git a/pkg/kubectl/cmd/exec.go b/pkg/kubectl/cmd/exec.go index 132cf8eb7a1..6674cb89328 100644 --- a/pkg/kubectl/cmd/exec.go +++ b/pkg/kubectl/cmd/exec.go @@ -22,13 +22,13 @@ import ( "net/url" dockerterm "github.com/docker/docker/pkg/term" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/remotecommand" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand" "k8s.io/kubernetes/pkg/util/interrupt" @@ -36,7 +36,7 @@ import ( ) var ( - exec_example = dedent.Dedent(` + exec_example = templates.Examples(` # Get output from running 'date' from pod 123456-7890, using the first container by default kubectl exec 123456-7890 date diff --git a/pkg/kubectl/cmd/explain.go b/pkg/kubectl/cmd/explain.go index 18133b705bf..dd22c6c1f04 100644 --- a/pkg/kubectl/cmd/explain.go +++ b/pkg/kubectl/cmd/explain.go @@ -20,27 +20,27 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) var ( - explainExamples = dedent.Dedent(` + explainLong = templates.LongDesc(` + Documentation of resources. + + ` + valid_resources) + + explainExamples = templates.Examples(` # Get the documentation of the resource and its fields kubectl explain pods # Get the documentation of a specific field of a resource kubectl explain pods.spec.containers`) - - explainLong = dedent.Dedent(` - Documentation of resources. - - `) + valid_resources ) // NewCmdExplain returns a cobra command for swagger docs diff --git a/pkg/kubectl/cmd/expose.go b/pkg/kubectl/cmd/expose.go index 06eb4f8b41f..b56e201c069 100644 --- a/pkg/kubectl/cmd/expose.go +++ b/pkg/kubectl/cmd/expose.go @@ -22,10 +22,10 @@ import ( "regexp" "strings" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -33,12 +33,9 @@ import ( ) var ( - expose_resources = dedent.Dedent(` - pod (po), service (svc), replicationcontroller (rc), - deployment (deploy), replicaset (rs) - `) + expose_resources = `pod (po), service (svc), replicationcontroller (rc), deployment (deploy), replicaset (rs)` - expose_long = dedent.Dedent(` + expose_long = templates.LongDesc(` Expose a resource as a new Kubernetes service. Looks up a deployment, service, replica set, replication controller or pod by name and uses the selector @@ -48,9 +45,11 @@ var ( --port and the exposed resource has multiple ports, all will be re-used by the new service. Also if no labels are specified, the new service will re-use the labels from the resource it exposes. - Possible resources include (case insensitive): `) + expose_resources + Possible resources include (case insensitive): - expose_example = dedent.Dedent(` + ` + expose_resources) + + expose_example = templates.Examples(` # Create a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000. kubectl expose rc nginx --port=80 --target-port=8000 diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index 0bce6801d8d..bca0f8ecb34 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -20,12 +20,12 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "github.com/golang/glog" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -43,17 +43,18 @@ type GetOptions struct { } var ( - get_long = dedent.Dedent(` + get_long = templates.LongDesc(` Display one or many resources. - `) + valid_resources + dedent.Dedent(` + ` + valid_resources + ` This command will hide resources that have completed. For instance, pods that are in the Succeeded or Failed phases. You can see the full results for any resource by providing the '--show-all' flag. By specifying the output as 'template' and providing a Go template as the value of the --template flag, you can filter the attributes of the fetched resource(s).`) - get_example = dedent.Dedent(` + + get_example = templates.Examples(` # List all pods in ps output format. kubectl get pods diff --git a/pkg/kubectl/cmd/help.go b/pkg/kubectl/cmd/help.go index e696b68d8ef..bee07bef0ea 100644 --- a/pkg/kubectl/cmd/help.go +++ b/pkg/kubectl/cmd/help.go @@ -22,11 +22,13 @@ import ( "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) -const help_long = `Help provides help for any command in the application. -Simply type kubectl help [path to command] for full details.` +var help_long = templates.LongDesc(` + Help provides help for any command in the application. + Simply type kubectl help [path to command] for full details.`) func NewCmdHelp(f cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ diff --git a/pkg/kubectl/cmd/label.go b/pkg/kubectl/cmd/label.go index 4a4b53783c3..99e853056d9 100644 --- a/pkg/kubectl/cmd/label.go +++ b/pkg/kubectl/cmd/label.go @@ -24,11 +24,11 @@ import ( "strings" "github.com/golang/glog" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -61,13 +61,14 @@ type LabelOptions struct { } var ( - label_long = dedent.Dedent(` + label_long = templates.LongDesc(` Update the labels on a resource. - A label must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters. - If --overwrite is true, then existing labels can be overwritten, otherwise attempting to overwrite a label will result in an error. - If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used.`) - label_example = dedent.Dedent(` + * A label must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters. + * If --overwrite is true, then existing labels can be overwritten, otherwise attempting to overwrite a label will result in an error. + * If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used.`) + + label_example = templates.Examples(` # Update pod 'foo' with the label 'unhealthy' and the value 'true'. kubectl label pods foo unhealthy=true diff --git a/pkg/kubectl/cmd/logs.go b/pkg/kubectl/cmd/logs.go index cd1d3dc8175..bcb8fe4ea90 100644 --- a/pkg/kubectl/cmd/logs.go +++ b/pkg/kubectl/cmd/logs.go @@ -23,20 +23,20 @@ import ( "os" "time" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" ) var ( - logs_example = dedent.Dedent(` + logs_example = templates.Examples(` # Return snapshot logs from pod nginx with only one container kubectl logs nginx diff --git a/pkg/kubectl/cmd/patch.go b/pkg/kubectl/cmd/patch.go index 7a6ca0a707e..6debf1f8624 100644 --- a/pkg/kubectl/cmd/patch.go +++ b/pkg/kubectl/cmd/patch.go @@ -22,11 +22,11 @@ import ( "strings" "github.com/evanphx/json-patch" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -48,14 +48,14 @@ type PatchOptions struct { } var ( - patch_long = dedent.Dedent(` + patch_long = templates.LongDesc(` Update field(s) of a resource using strategic merge patch JSON and YAML formats are accepted. Please refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable.`) - patch_example = dedent.Dedent(` + patch_example = templates.Examples(` # Partially update a node using strategic merge patch kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}' diff --git a/pkg/kubectl/cmd/portforward.go b/pkg/kubectl/cmd/portforward.go index df03d4c0b4b..b00f81fa775 100644 --- a/pkg/kubectl/cmd/portforward.go +++ b/pkg/kubectl/cmd/portforward.go @@ -23,7 +23,6 @@ import ( "os" "os/signal" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" @@ -31,6 +30,7 @@ import ( "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/portforward" "k8s.io/kubernetes/pkg/client/unversioned/remotecommand" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) @@ -48,7 +48,7 @@ type PortForwardOptions struct { } var ( - portforward_example = dedent.Dedent(` + portforward_example = templates.Examples(` # Listen on ports 5000 and 6000 locally, forwarding data to/from ports 5000 and 6000 in the pod kubectl port-forward mypod 5000 6000 diff --git a/pkg/kubectl/cmd/proxy.go b/pkg/kubectl/cmd/proxy.go index 1714d133f88..03135f044ca 100644 --- a/pkg/kubectl/cmd/proxy.go +++ b/pkg/kubectl/cmd/proxy.go @@ -24,15 +24,32 @@ import ( "strings" "github.com/golang/glog" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) var ( - default_port = 8001 - proxy_example = dedent.Dedent(` + default_port = 8001 + proxy_long = templates.LongDesc(` + To proxy all of the kubernetes api and nothing else, use: + + $ kubectl proxy --api-prefix=/ + + To proxy only part of the kubernetes api and also some static files: + + $ kubectl proxy --www=/my/files --www-prefix=/static/ --api-prefix=/api/ + + The above lets you 'curl localhost:8001/api/v1/pods'. + + To proxy the entire kubernetes api at a different root, use: + + $ kubectl proxy --api-prefix=/custom/ + + The above lets you 'curl localhost:8001/custom/api/v1/pods'`) + + proxy_example = templates.Examples(` # Run a proxy to kubernetes apiserver on port 8011, serving static content from ./local/www/ kubectl proxy --port=8011 --www=./local/www/ @@ -47,25 +64,9 @@ var ( func NewCmdProxy(f cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix]", - Short: "Run a proxy to the Kubernetes API server", - Long: dedent.Dedent(` - To proxy all of the kubernetes api and nothing else, use: - - kubectl proxy --api-prefix=/ - - To proxy only part of the kubernetes api and also some static files: - - kubectl proxy --www=/my/files --www-prefix=/static/ --api-prefix=/api/ - - The above lets you 'curl localhost:8001/api/v1/pods'. - - To proxy the entire kubernetes api at a different root, use: - - kubectl proxy --api-prefix=/custom/ - - The above lets you 'curl localhost:8001/custom/api/v1/pods' - `), + Use: "proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix]", + Short: "Run a proxy to the Kubernetes API server", + Long: proxy_long, Example: proxy_example, Run: func(cmd *cobra.Command, args []string) { err := RunProxy(f, out, cmd) diff --git a/pkg/kubectl/cmd/replace.go b/pkg/kubectl/cmd/replace.go index 91e7d772ff8..d705c414b1b 100644 --- a/pkg/kubectl/cmd/replace.go +++ b/pkg/kubectl/cmd/replace.go @@ -23,12 +23,12 @@ import ( "os" "path/filepath" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "github.com/golang/glog" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -36,15 +36,17 @@ import ( ) var ( - replace_long = dedent.Dedent(` + replace_long = templates.LongDesc(` Replace a resource by filename or stdin. JSON and YAML formats are accepted. If replacing an existing resource, the complete resource spec must be provided. This can be obtained by - $ kubectl get TYPE NAME -o yaml + + $ kubectl get TYPE NAME -o yaml Please refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable.`) - replace_example = dedent.Dedent(` + + replace_example = templates.Examples(` # Replace a pod using the data in pod.json. kubectl replace -f ./pod.json diff --git a/pkg/kubectl/cmd/rollingupdate.go b/pkg/kubectl/cmd/rollingupdate.go index 1de87ee0d24..0b817925d6d 100644 --- a/pkg/kubectl/cmd/rollingupdate.go +++ b/pkg/kubectl/cmd/rollingupdate.go @@ -25,7 +25,6 @@ import ( "github.com/golang/glog" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" @@ -33,6 +32,7 @@ import ( "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" utilerrors "k8s.io/kubernetes/pkg/util/errors" @@ -40,16 +40,16 @@ import ( ) var ( - rollingUpdate_long = dedent.Dedent(` + rollingUpdate_long = templates.LongDesc(` Perform a rolling update of the given ReplicationController. Replaces the specified replication controller with a new replication controller by updating one pod at a time to use the new PodTemplate. The new-controller.json must specify the same namespace as the existing replication controller and overwrite at least one (common) label in its replicaSelector. - ![Workflow](http://kubernetes.io/images/docs/kubectl_rollingupdate.svg) -`) - rollingUpdate_example = dedent.Dedent(` + ![Workflow](http://kubernetes.io/images/docs/kubectl_rollingupdate.svg)`) + + rollingUpdate_example = templates.Examples(` # Update pods of frontend-v1 using new replication controller data in frontend-v2.json. kubectl rolling-update frontend-v1 -f frontend-v2.json @@ -64,8 +64,7 @@ var ( kubectl rolling-update frontend --image=image:v2 # Abort and reverse an existing rollout in progress (from frontend-v1 to frontend-v2). - kubectl rolling-update frontend-v1 frontend-v2 --rollback - `) + kubectl rolling-update frontend-v1 frontend-v2 --rollback`) ) var ( diff --git a/pkg/kubectl/cmd/rollout/rollout.go b/pkg/kubectl/cmd/rollout/rollout.go index 7972478e181..1f9a6870e8d 100644 --- a/pkg/kubectl/cmd/rollout/rollout.go +++ b/pkg/kubectl/cmd/rollout/rollout.go @@ -21,15 +21,18 @@ import ( "github.com/renstrom/dedent" "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) var ( - rollout_long = dedent.Dedent(` + rollout_long = templates.LongDesc(` Manage a deployment using subcommands like "kubectl rollout undo deployment/abc"`) - rollout_example = dedent.Dedent(` + + rollout_example = templates.Examples(` # Rollback to the previous deployment kubectl rollout undo deployment/abc`) + rollout_valid_resources = dedent.Dedent(` Valid resource types include: * deployments diff --git a/pkg/kubectl/cmd/rollout/rollout_history.go b/pkg/kubectl/cmd/rollout/rollout_history.go index 32a6866a3aa..2b14e37dc17 100644 --- a/pkg/kubectl/cmd/rollout/rollout_history.go +++ b/pkg/kubectl/cmd/rollout/rollout_history.go @@ -20,8 +20,8 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -29,9 +29,10 @@ import ( ) var ( - history_long = dedent.Dedent(` + history_long = templates.LongDesc(` View previous rollout revisions and configurations.`) - history_example = dedent.Dedent(` + + history_example = templates.Examples(` # View the rollout history of a deployment kubectl rollout history deployment/abc diff --git a/pkg/kubectl/cmd/rollout/rollout_pause.go b/pkg/kubectl/cmd/rollout/rollout_pause.go index 0eeef58cba3..33905faf05f 100644 --- a/pkg/kubectl/cmd/rollout/rollout_pause.go +++ b/pkg/kubectl/cmd/rollout/rollout_pause.go @@ -19,11 +19,11 @@ package rollout import ( "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -44,14 +44,14 @@ type PauseConfig struct { } var ( - pause_long = dedent.Dedent(` + pause_long = templates.LongDesc(` Mark the provided resource as paused Paused resources will not be reconciled by a controller. Use \"kubectl rollout resume\" to resume a paused resource. Currently only deployments support being paused.`) - pause_example = dedent.Dedent(` + pause_example = templates.Examples(` # Mark the nginx deployment as paused. Any current state of # the deployment will continue its function, new updates to the deployment will not # have an effect as long as the deployment is paused. diff --git a/pkg/kubectl/cmd/rollout/rollout_resume.go b/pkg/kubectl/cmd/rollout/rollout_resume.go index 4e958d5a8e5..74265f901f1 100644 --- a/pkg/kubectl/cmd/rollout/rollout_resume.go +++ b/pkg/kubectl/cmd/rollout/rollout_resume.go @@ -19,11 +19,11 @@ package rollout import ( "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -44,14 +44,14 @@ type ResumeConfig struct { } var ( - resume_long = dedent.Dedent(` + resume_long = templates.LongDesc(` Resume a paused resource Paused resources will not be reconciled by a controller. By resuming a resource, we allow it to be reconciled again. Currently only deployments support being resumed.`) - resume_example = dedent.Dedent(` + resume_example = templates.Examples(` # Resume an already paused deployment kubectl rollout resume deployment/nginx`) ) diff --git a/pkg/kubectl/cmd/rollout/rollout_status.go b/pkg/kubectl/cmd/rollout/rollout_status.go index 13e7bd6d917..b629e439744 100644 --- a/pkg/kubectl/cmd/rollout/rollout_status.go +++ b/pkg/kubectl/cmd/rollout/rollout_status.go @@ -20,8 +20,8 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/util/interrupt" @@ -31,7 +31,7 @@ import ( ) var ( - status_long = dedent.Dedent(` + status_long = templates.LongDesc(` Show the status of the rollout. By default 'rollout status' will watch the status of the latest rollout @@ -40,7 +40,8 @@ var ( 'rollout status' will continue watching the latest revision. If you want to pin to a specific revision and abort if it is rolled over by another revision, use --revision=N where N is the revision you need to watch for.`) - status_example = dedent.Dedent(` + + status_example = templates.Examples(` # Watch the rollout status of a deployment kubectl rollout status deployment/nginx`) ) diff --git a/pkg/kubectl/cmd/rollout/rollout_undo.go b/pkg/kubectl/cmd/rollout/rollout_undo.go index 2efc999581b..ca2f48830d5 100644 --- a/pkg/kubectl/cmd/rollout/rollout_undo.go +++ b/pkg/kubectl/cmd/rollout/rollout_undo.go @@ -19,9 +19,9 @@ package rollout import ( "io" - "github.com/renstrom/dedent" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -46,9 +46,10 @@ type UndoOptions struct { } var ( - undo_long = dedent.Dedent(` + undo_long = templates.LongDesc(` Rollback to a previous rollout.`) - undo_example = dedent.Dedent(` + + undo_example = templates.Examples(` # Rollback to the previous deployment kubectl rollout undo deployment/abc diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index 29fdf2491f2..bd937656ec4 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -22,7 +22,6 @@ import ( "os" "time" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "github.com/docker/distribution/reference" @@ -36,6 +35,7 @@ import ( coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" conditions "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -45,10 +45,12 @@ import ( ) var ( - run_long = dedent.Dedent(` + run_long = templates.LongDesc(` Create and run a particular image, possibly replicated. + Creates a deployment or job to manage the created container(s).`) - run_example = dedent.Dedent(` + + run_example = templates.Examples(` # Start a single instance of nginx. kubectl run nginx --image=nginx diff --git a/pkg/kubectl/cmd/scale.go b/pkg/kubectl/cmd/scale.go index e3d03de9bf2..c7f266b70d2 100644 --- a/pkg/kubectl/cmd/scale.go +++ b/pkg/kubectl/cmd/scale.go @@ -21,24 +21,26 @@ import ( "io" "os" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" ) var ( - scale_long = dedent.Dedent(` + scale_long = templates.LongDesc(` Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job. Scale also allows users to specify one or more preconditions for the scale action. + If --current-replicas or --resource-version is specified, it is validated before the scale is attempted, and it is guaranteed that the precondition holds true when the scale is sent to the server.`) - scale_example = dedent.Dedent(` + + scale_example = templates.Examples(` # Scale a replicaset named 'foo' to 3. kubectl scale --replicas=3 rs/foo diff --git a/pkg/kubectl/cmd/set/set.go b/pkg/kubectl/cmd/set/set.go index c15f5264f6e..01a0b29a480 100644 --- a/pkg/kubectl/cmd/set/set.go +++ b/pkg/kubectl/cmd/set/set.go @@ -19,25 +19,23 @@ package set import ( "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) var ( - set_long = dedent.Dedent(` + set_long = templates.LongDesc(` Configure application resources - + These commands help you make changes to existing application resources.`) - set_example = dedent.Dedent(``) ) func NewCmdSet(f cmdutil.Factory, out, err io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "set SUBCOMMAND", - Short: "Set specific features on objects", - Long: set_long, - Example: set_example, + Use: "set SUBCOMMAND", + Short: "Set specific features on objects", + Long: set_long, Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, diff --git a/pkg/kubectl/cmd/set/set_image.go b/pkg/kubectl/cmd/set/set_image.go index fdfb00d6fb1..d2477219eee 100644 --- a/pkg/kubectl/cmd/set/set_image.go +++ b/pkg/kubectl/cmd/set/set_image.go @@ -20,10 +20,10 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -57,14 +57,15 @@ type ImageOptions struct { var ( image_resources = ` - pod (po), replicationcontroller (rc), deployment (deploy), daemonset (ds), job, replicaset (rs)` + pod (po), replicationcontroller (rc), deployment (deploy), daemonset (ds), job, replicaset (rs)` - image_long = dedent.Dedent(` + image_long = templates.LongDesc(` Update existing container image(s) of resources. - Possible resources include (case insensitive):`) + image_resources + Possible resources include (case insensitive): + ` + image_resources) - image_example = dedent.Dedent(` + image_example = templates.Examples(` # Set a deployment's nginx container image to 'nginx:1.9.1', and its busybox container image to 'busybox'. kubectl set image deployment/nginx busybox=busybox nginx=nginx:1.9.1 @@ -74,7 +75,7 @@ var ( # Update image of all containers of daemonset abc to 'nginx:1.9.1' kubectl set image daemonset abc *=nginx:1.9.1 - # Print result (in yaml format) of updating nginx container image from local file, without hitting the server + # Print result (in yaml format) of updating nginx container image from local file, without hitting the server kubectl set image -f path/to/file.yaml nginx=nginx:1.9.1 --local -o yaml`) ) diff --git a/pkg/kubectl/cmd/set/set_resources.go b/pkg/kubectl/cmd/set/set_resources.go index dcb97e6d387..a95362a27bc 100644 --- a/pkg/kubectl/cmd/set/set_resources.go +++ b/pkg/kubectl/cmd/set/set_resources.go @@ -25,12 +25,35 @@ import ( "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" utilerrors "k8s.io/kubernetes/pkg/util/errors" ) +var ( + resources_long = templates.LongDesc(` + Specify compute resource requirements (cpu, memory) for any resource that defines a pod template. If a pod is successfully scheduled, it is guaranteed the amount of resource requested, but may burst up to its specified limits. + + for each compute resource, if a limit is specified and a request is omitted, the request will default to the limit. + + Possible resources include (case insensitive):`) + + resources_example = templates.Examples(` + # Set a deployments nginx container cpu limits to "200m" and memory to "512Mi" + kubectl set resources deployment nginx -c=nginx --limits=cpu=200m,memory=512Mi + + # Set the resource request and limits for all containers in nginx + kubectl set resources deployment nginx --limits=cpu=200m,memory=512Mi --requests=cpu=100m,memory=256Mi + + # Remove the resource requests for resources on containers in nginx + kubectl set resources deployment nginx --limits=cpu=0,memory=0 --requests=cpu=0,memory=0 + + # Print the result (in yaml format) of updating nginx container limits from a local, without hitting the server + kubectl set resources -f path/to/file.yaml --limits=cpu=200m,memory=512Mi --dry-run -o yaml`) +) + // ResourcesOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of // referencing the cmd.Flags type ResourcesOptions struct { @@ -59,32 +82,6 @@ type ResourcesOptions struct { Resources []string } -const ( - resources_long = `Specify compute resource requirements (cpu, memory) for any resource that defines a pod template. If a pod is successfully scheduled, it is guaranteed the amount of resource requested, but may burst up to its specified limits. - -for each compute resource, if a limit is specified and a request is omitted, the request will default to the limit. - -Possible resources include (case insensitive):` - - resources_example = ` -# Set a deployments nginx container cpu limits to "200m and memory to "512Mi" - -kubectl set resources deployment nginx -c=nginx --limits=cpu=200m,memory=512Mi - -# Set the resource request and limits for all containers in nginx - -kubectl set resources deployment nginx --limits=cpu=200m,memory=512Mi --requests=cpu=100m,memory=256Mi - -# Remove the resource requests for resources on containers in nginx - -kubectl set resources deployment nginx --limits=cpu=0,memory=0 --requests=cpu=0,memory=0 - -# Print the result (in yaml format) of updating nginx container limits from a local, without hitting the server - -kubectl set resources -f path/to/file.yaml --limits=cpu=200m,memory=512Mi --dry-run -o yaml -` -) - func NewCmdResources(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command { options := &ResourcesOptions{ Out: out, @@ -100,7 +97,7 @@ func NewCmdResources(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra. cmd := &cobra.Command{ Use: "resources (-f FILENAME | TYPE NAME) ([--limits=LIMITS & --requests=REQUESTS]", Short: "update resource requests/limits on objects with pod templates", - Long: resources_long + "\n" + pod_specs[2:], + Long: resources_long + " " + pod_specs[2:], Example: resources_example, Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(options.Complete(f, cmd, args)) diff --git a/pkg/kubectl/cmd/stop.go b/pkg/kubectl/cmd/stop.go index b3c74f54e88..968de529a26 100644 --- a/pkg/kubectl/cmd/stop.go +++ b/pkg/kubectl/cmd/stop.go @@ -20,14 +20,14 @@ import ( "fmt" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" ) var ( - stop_long = dedent.Dedent(` + stop_long = templates.LongDesc(` Deprecated: Gracefully shut down a resource by name or filename. The stop command is deprecated, all its functionalities are covered by delete command. @@ -35,7 +35,8 @@ var ( Attempts to shut down and delete a resource that supports graceful termination. If the resource is scalable it will be scaled to 0 before deletion.`) - stop_example = dedent.Dedent(` + + stop_example = templates.Examples(` # Shut down foo. kubectl stop replicationcontroller foo diff --git a/pkg/kubectl/cmd/taint.go b/pkg/kubectl/cmd/taint.go index 6e6cb76ffc2..142137f587d 100644 --- a/pkg/kubectl/cmd/taint.go +++ b/pkg/kubectl/cmd/taint.go @@ -24,11 +24,11 @@ import ( "encoding/json" "github.com/golang/glog" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -53,15 +53,16 @@ type TaintOptions struct { } var ( - taint_long = dedent.Dedent(` + taint_long = templates.LongDesc(` Update the taints on one or more nodes. - A taint consists of a key, value, and effect. As an argument here, it is expressed as key=value:effect. - The key must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters. - The value must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters. - The effect must be NoSchedule or PreferNoSchedule. - Currently taint can only apply to node.`) - taint_example = dedent.Dedent(` + * A taint consists of a key, value, and effect. As an argument here, it is expressed as key=value:effect. + * The key must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters. + * The value must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters. + * The effect must be NoSchedule or PreferNoSchedule. + * Currently taint can only apply to node.`) + + taint_example = templates.Examples(` # Update node 'foo' with a taint with key 'dedicated' and value 'special-user' and effect 'NoSchedule'. # If a taint with that key and effect already exists, its value is replaced as specified. kubectl taint nodes foo dedicated=special-user:NoSchedule diff --git a/pkg/kubectl/cmd/top.go b/pkg/kubectl/cmd/top.go index 6d2b94a2b5b..af4f27cc3b1 100644 --- a/pkg/kubectl/cmd/top.go +++ b/pkg/kubectl/cmd/top.go @@ -21,15 +21,15 @@ import ( cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - "github.com/renstrom/dedent" "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" ) // TopOptions contains all the options for running the top cli command. type TopOptions struct{} var ( - topLong = dedent.Dedent(` + topLong = templates.LongDesc(` Display Resource (CPU/Memory/Storage) usage. The top command allows you to see the resource consumption for nodes or pods.`) diff --git a/pkg/kubectl/cmd/top_node.go b/pkg/kubectl/cmd/top_node.go index 43f2542415e..b02b15ada67 100644 --- a/pkg/kubectl/cmd/top_node.go +++ b/pkg/kubectl/cmd/top_node.go @@ -20,11 +20,11 @@ import ( "errors" "io" - "github.com/renstrom/dedent" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/metricsutil" "k8s.io/kubernetes/pkg/labels" @@ -40,12 +40,12 @@ type TopNodeOptions struct { } var ( - topNodeLong = dedent.Dedent(` + topNodeLong = templates.LongDesc(` Display Resource (CPU/Memory/Storage) usage of nodes. The top-node command allows you to see the resource consumption of nodes.`) - topNodeExample = dedent.Dedent(` + topNodeExample = templates.Examples(` # Show metrics for all nodes kubectl top node diff --git a/pkg/kubectl/cmd/top_pod.go b/pkg/kubectl/cmd/top_pod.go index d8894de318b..c4931de0ab2 100644 --- a/pkg/kubectl/cmd/top_pod.go +++ b/pkg/kubectl/cmd/top_pod.go @@ -24,12 +24,12 @@ import ( "k8s.io/kubernetes/pkg/api" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/metricsutil" "k8s.io/kubernetes/pkg/labels" "github.com/golang/glog" - "github.com/renstrom/dedent" "github.com/spf13/cobra" ) @@ -47,7 +47,7 @@ type TopPodOptions struct { const metricsCreationDelay = 2 * time.Minute var ( - topPodLong = dedent.Dedent(` + topPodLong = templates.LongDesc(` Display Resource (CPU/Memory/Storage) usage of pods. The 'top pod' command allows you to see the resource consumption of pods. @@ -55,18 +55,18 @@ var ( Due to the metrics pipeline delay, they may be unavailable for a few minutes since pod creation.`) - topPodExample = dedent.Dedent(` - # Show metrics for all pods in the default namespace - kubectl top pod + topPodExample = templates.Examples(` + # Show metrics for all pods in the default namespace + kubectl top pod - # Show metrics for all pods in the given namespace - kubectl top pod --namespace=NAMESPACE + # Show metrics for all pods in the given namespace + kubectl top pod --namespace=NAMESPACE - # Show metrics for a given pod and its containers - kubectl top pod POD_NAME --containers + # Show metrics for a given pod and its containers + kubectl top pod POD_NAME --containers - # Show metrics for the pods defined by label name=myLabel - kubectl top pod -l name=myLabel`) + # Show metrics for the pods defined by label name=myLabel + kubectl top pod -l name=myLabel`) ) func NewCmdTopPod(f cmdutil.Factory, out io.Writer) *cobra.Command { From 4d641c491171586b5c033cfd81566049e6059470 Mon Sep 17 00:00:00 2001 From: Fabiano Franz Date: Mon, 10 Oct 2016 15:39:16 -0300 Subject: [PATCH 5/7] Use responsive writer in help --- pkg/kubectl/cmd/cmd.go | 27 --- pkg/kubectl/cmd/templates/command_groups.go | 61 +++++++ pkg/kubectl/cmd/templates/templater.go | 185 ++++++++++---------- pkg/kubectl/cmd/templates/templates.go | 119 +++++++------ 4 files changed, 211 insertions(+), 181 deletions(-) create mode 100644 pkg/kubectl/cmd/templates/command_groups.go diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 3c70434df13..d1babebef5c 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -195,33 +195,6 @@ __custom_func() { * serviceaccounts (aka 'sa') * services (aka 'svc') ` - usage_template = `{{if gt .Aliases 0}} - -Aliases: - {{.NameAndAliases}}{{end}}{{if .HasExample}} - -Examples: -{{ .Example }}{{end}}{{ if .HasAvailableSubCommands}} - -Available Sub-commands:{{range .Commands}}{{if .IsAvailableCommand}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasLocalFlags}} - -Flags: -{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasInheritedFlags}} - -Global Flags: -{{.InheritedFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasHelpSubCommands}} - -Additional help topics:{{range .Commands}}{{if .IsHelpCommand}} - {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}} - -Usage:{{if .Runnable}} - {{if .HasFlags}}{{appendIfNotPresent .UseLine "[flags]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{ if .HasSubCommands }} - {{ .CommandPath}} [command] - -Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} -` - help_template = `{{with or .Long .Short }}{{. | trim}}{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` ) // NewKubectlCommand creates the `kubectl` command and its nested children. diff --git a/pkg/kubectl/cmd/templates/command_groups.go b/pkg/kubectl/cmd/templates/command_groups.go new file mode 100644 index 00000000000..42baa85a607 --- /dev/null +++ b/pkg/kubectl/cmd/templates/command_groups.go @@ -0,0 +1,61 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "github.com/spf13/cobra" +) + +type CommandGroup struct { + Message string + Commands []*cobra.Command +} + +type CommandGroups []CommandGroup + +func (g CommandGroups) Add(c *cobra.Command) { + for _, group := range g { + for _, command := range group.Commands { + c.AddCommand(command) + } + } +} + +func (g CommandGroups) Has(c *cobra.Command) bool { + for _, group := range g { + for _, command := range group.Commands { + if command == c { + return true + } + } + } + return false +} + +func AddAdditionalCommands(g CommandGroups, message string, cmds []*cobra.Command) CommandGroups { + group := CommandGroup{Message: message} + for _, c := range cmds { + // Don't show commands that have no short description + if !g.Has(c) && len(c.Short) != 0 { + group.Commands = append(group.Commands, c) + } + } + if len(group.Commands) == 0 { + return g + } + return append(g, group) +} diff --git a/pkg/kubectl/cmd/templates/templater.go b/pkg/kubectl/cmd/templates/templater.go index b51f45a1630..30c128ef3b2 100644 --- a/pkg/kubectl/cmd/templates/templater.go +++ b/pkg/kubectl/cmd/templates/templater.go @@ -23,73 +23,12 @@ import ( "text/template" "unicode" + "k8s.io/kubernetes/pkg/util/term" + "github.com/spf13/cobra" flag "github.com/spf13/pflag" ) -// Content of this package was borrowed from openshift/origin. - -type CommandGroup struct { - Message string - Commands []*cobra.Command -} - -type CommandGroups []CommandGroup - -func (g CommandGroups) Add(c *cobra.Command) { - for _, group := range g { - for _, command := range group.Commands { - c.AddCommand(command) - } - } -} - -func (g CommandGroups) Has(c *cobra.Command) bool { - for _, group := range g { - for _, command := range group.Commands { - if command == c { - return true - } - } - } - return false -} - -func AddAdditionalCommands(g CommandGroups, message string, cmds []*cobra.Command) CommandGroups { - group := CommandGroup{Message: message} - for _, c := range cmds { - // Don't show commands that has no short description - if !g.Has(c) && len(c.Short) != 0 { - group.Commands = append(group.Commands, c) - } - } - if len(group.Commands) == 0 { - return g - } - return append(g, group) -} - -func filter(cmds []*cobra.Command, names ...string) []*cobra.Command { - out := []*cobra.Command{} - for _, c := range cmds { - if c.Hidden { - continue - } - skip := false - for _, name := range names { - if name == c.Name() { - skip = true - break - } - } - if skip { - continue - } - out = append(out, c) - } - return out -} - type FlagExposer interface { ExposeFlags(cmd *cobra.Command, flags ...string) FlagExposer } @@ -98,27 +37,30 @@ func ActsAsRootCommand(cmd *cobra.Command, filters []string, groups ...CommandGr if cmd == nil { panic("nil root command") } - cmd.SetHelpTemplate(MainHelpTemplate()) templater := &templater{ RootCmd: cmd, UsageTemplate: MainUsageTemplate(), + HelpTemplate: MainHelpTemplate(), CommandGroups: groups, Filtered: filters, } cmd.SetUsageFunc(templater.UsageFunc()) + cmd.SetHelpFunc(templater.HelpFunc()) return templater } func UseOptionsTemplates(cmd *cobra.Command) { - cmd.SetHelpTemplate(OptionsHelpTemplate()) templater := &templater{ UsageTemplate: OptionsUsageTemplate(), + HelpTemplate: OptionsHelpTemplate(), } cmd.SetUsageFunc(templater.UsageFunc()) + cmd.SetHelpFunc(templater.HelpFunc()) } type templater struct { UsageTemplate string + HelpTemplate string RootCmd *cobra.Command CommandGroups Filtered []string @@ -129,42 +71,58 @@ func (templater *templater) ExposeFlags(cmd *cobra.Command, flags ...string) Fla return templater } +func (templater *templater) HelpFunc() func(*cobra.Command, []string) { + return func(c *cobra.Command, s []string) { + t := template.New("help") + t.Funcs(templater.templateFuncs()) + template.Must(t.Parse(templater.HelpTemplate)) + out := term.NewResponsiveWriter(c.OutOrStdout()) + err := t.Execute(out, c) + if err != nil { + c.Println(err) + } + } +} + func (templater *templater) UsageFunc(exposedFlags ...string) func(*cobra.Command) error { return func(c *cobra.Command) error { - t := template.New("custom") + t := template.New("usage") + t.Funcs(templater.templateFuncs(exposedFlags...)) + template.Must(t.Parse(templater.UsageTemplate)) + out := term.NewResponsiveWriter(c.OutOrStderr()) + return t.Execute(out, c) + } +} - t.Funcs(template.FuncMap{ - "trim": strings.TrimSpace, - "trimRight": func(s string) string { return strings.TrimRightFunc(s, unicode.IsSpace) }, - "trimLeft": func(s string) string { return strings.TrimLeftFunc(s, unicode.IsSpace) }, - "gt": cobra.Gt, - "eq": cobra.Eq, - "rpad": rpad, - "appendIfNotPresent": appendIfNotPresent, - "flagsNotIntersected": flagsNotIntersected, - "visibleFlags": visibleFlags, - "flagsUsages": flagsUsages, - "indentLines": indentLines, - "cmdGroups": templater.cmdGroups, - "rootCmd": templater.rootCmdName, - "isRootCmd": templater.isRootCmd, - "optionsCmdFor": templater.optionsCmdFor, - "usageLine": templater.usageLine, - "exposed": func(c *cobra.Command) *flag.FlagSet { - exposed := flag.NewFlagSet("exposed", flag.ContinueOnError) - if len(exposedFlags) > 0 { - for _, name := range exposedFlags { - if flag := c.Flags().Lookup(name); flag != nil { - exposed.AddFlag(flag) - } +func (templater *templater) templateFuncs(exposedFlags ...string) template.FuncMap { + return template.FuncMap{ + "trim": strings.TrimSpace, + "trimRight": func(s string) string { return strings.TrimRightFunc(s, unicode.IsSpace) }, + "trimLeft": func(s string) string { return strings.TrimLeftFunc(s, unicode.IsSpace) }, + "gt": cobra.Gt, + "eq": cobra.Eq, + "rpad": rpad, + "appendIfNotPresent": appendIfNotPresent, + "flagsNotIntersected": flagsNotIntersected, + "visibleFlags": visibleFlags, + "flagsUsages": flagsUsages, + "cmdGroups": templater.cmdGroups, + "cmdGroupsString": templater.cmdGroupsString, + "rootCmd": templater.rootCmdName, + "isRootCmd": templater.isRootCmd, + "optionsCmdFor": templater.optionsCmdFor, + "usageLine": templater.usageLine, + "exposed": func(c *cobra.Command) *flag.FlagSet { + exposed := flag.NewFlagSet("exposed", flag.ContinueOnError) + if len(exposedFlags) > 0 { + for _, name := range exposedFlags { + if flag := c.Flags().Lookup(name); flag != nil { + exposed.AddFlag(flag) } } - return exposed - }, - }) - - template.Must(t.Parse(templater.UsageTemplate)) - return t.Execute(c.OutOrStdout(), c) + } + return exposed + }, } } @@ -182,6 +140,20 @@ func (templater *templater) cmdGroups(c *cobra.Command, all []*cobra.Command) [] } } +func (t *templater) cmdGroupsString(c *cobra.Command) string { + groups := []string{} + for _, cmdGroup := range t.cmdGroups(c, c.Commands()) { + cmds := []string{cmdGroup.Message} + for _, cmd := range cmdGroup.Commands { + if cmd.Runnable() { + cmds = append(cmds, " "+rpad(cmd.Name(), cmd.NamePadding())+" "+cmd.Short) + } + } + groups = append(groups, strings.Join(cmds, "\n")) + } + return strings.Join(groups, "\n\n") +} + func (t *templater) rootCmdName(c *cobra.Command) string { return t.rootCmd(c).CommandPath() } @@ -298,3 +270,24 @@ func visibleFlags(l *flag.FlagSet) *flag.FlagSet { }) return f } + +func filter(cmds []*cobra.Command, names ...string) []*cobra.Command { + out := []*cobra.Command{} + for _, c := range cmds { + if c.Hidden { + continue + } + skip := false + for _, name := range names { + if name == c.Name() { + skip = true + break + } + } + if skip { + continue + } + out = append(out, c) + } + return out +} diff --git a/pkg/kubectl/cmd/templates/templates.go b/pkg/kubectl/cmd/templates/templates.go index 45a4aa3fd7a..c40908f9f60 100644 --- a/pkg/kubectl/cmd/templates/templates.go +++ b/pkg/kubectl/cmd/templates/templates.go @@ -16,84 +16,87 @@ limitations under the License. package templates -import "strings" - -func MainHelpTemplate() string { - return decorate(mainHelpTemplate, false) -} - -func MainUsageTemplate() string { - return decorate(mainUsageTemplate, true) + "\n" -} - -func OptionsHelpTemplate() string { - return decorate(optionsHelpTemplate, false) -} - -func OptionsUsageTemplate() string { - return decorate(optionsUsageTemplate, false) -} - -func decorate(template string, trim bool) string { - if trim && len(strings.Trim(template, " ")) > 0 { - template = strings.Trim(template, "\n") - } - return template -} +import ( + "strings" + "unicode" +) const ( - vars = `{{$isRootCmd := isRootCmd .}}` + + // SectionVars is the help template section that declares variables to be used in the template. + SectionVars = `{{$isRootCmd := isRootCmd .}}` + `{{$rootCmd := rootCmd .}}` + `{{$visibleFlags := visibleFlags (flagsNotIntersected .LocalFlags .PersistentFlags)}}` + `{{$explicitlyExposedFlags := exposed .}}` + `{{$optionsCmdFor := optionsCmdFor .}}` + `{{$usageLine := usageLine .}}` - mainHelpTemplate = `{{with or .Long .Short }}{{. | trim}}{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` + // SectionAliases is the help template section that displays command aliases. + SectionAliases = `{{if gt .Aliases 0}}Aliases: +{{.NameAndAliases}} - mainUsageTemplate = vars + - // ALIASES - `{{if gt .Aliases 0}} +{{end}}` -Aliases: -{{.NameAndAliases}}{{end}}` + + // SectionExamples is the help template section that displays command examples. + SectionExamples = `{{if .HasExample}}Examples: +{{trimRight .Example}} - // EXAMPLES - `{{if .HasExample}} +{{end}}` -Examples: -{{ .Example}}{{end}}` + + // SectionSubcommands is the help template section that displays the command's subcommands. + SectionSubcommands = `{{if .HasAvailableSubCommands}}{{cmdGroupsString .}} - // SUBCOMMANDS - `{{ if .HasAvailableSubCommands}} -{{range cmdGroups . .Commands}} -{{.Message}} -{{range .Commands}}{{if .Runnable}} {{rpad .Name .NamePadding }} {{.Short}} -{{end}}{{end}}{{end}}{{end}}` + +{{end}}` - // VISIBLE FLAGS - `{{ if or $visibleFlags.HasFlags $explicitlyExposedFlags.HasFlags}} + // SectionFlags is the help template section that displays the command's flags. + SectionFlags = `{{ if or $visibleFlags.HasFlags $explicitlyExposedFlags.HasFlags}}Options: +{{ if $visibleFlags.HasFlags}}{{trimRight (flagsUsages $visibleFlags)}}{{end}}{{ if $explicitlyExposedFlags.HasFlags}}{{trimRight (flagsUsages $explicitlyExposedFlags)}}{{end}} -Options: -{{ if $visibleFlags.HasFlags}}{{flagsUsages $visibleFlags}}{{end}}{{ if $explicitlyExposedFlags.HasFlags}}{{flagsUsages $explicitlyExposedFlags}}{{end}}{{end}}` + +{{end}}` - // USAGE LINE - `{{if and .Runnable (ne .UseLine "") (ne .UseLine $rootCmd)}} -Usage: + // SectionUsage is the help template section that displays the command's usage. + SectionUsage = `{{if and .Runnable (ne .UseLine "") (ne .UseLine $rootCmd)}}Usage: {{$usageLine}} -{{end}}` + - // TIPS: --help - `{{ if .HasSubCommands }} -Use "{{$rootCmd}} --help" for more information about a given command.{{end}}` + +{{end}}` - // TIPS: global options - `{{ if $optionsCmdFor}} -Use "{{$optionsCmdFor}}" for a list of global command-line options (applies to all commands).{{end}}` + // SectionTipsHelp is the help template section that displays the '--help' hint. + SectionTipsHelp = `{{if .HasSubCommands}}Use "{{$rootCmd}} --help" for more information about a given command. +{{end}}` - optionsHelpTemplate = `` + // SectionTipsGlobalOptions is the help template section that displays the 'options' hint for displaying global flags. + SectionTipsGlobalOptions = `{{if $optionsCmdFor}}Use "{{$optionsCmdFor}}" for a list of global command-line options (applies to all commands). +{{end}}` +) - optionsUsageTemplate = `{{ if .HasInheritedFlags}}The following options can be passed to any command: +// MainHelpTemplate if the template for 'help' used by most commands. +func MainHelpTemplate() string { + return `{{with or .Long .Short }}{{. | trim}}{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` +} + +// MainUsageTemplate if the template for 'usage' used by most commands. +func MainUsageTemplate() string { + sections := []string{ + "\n\n", + SectionVars, + SectionAliases, + SectionExamples, + SectionSubcommands, + SectionFlags, + SectionUsage, + SectionTipsHelp, + SectionTipsGlobalOptions, + } + return strings.TrimRightFunc(strings.Join(sections, ""), unicode.IsSpace) +} + +// OptionsHelpTemplate if the template for 'help' used by the 'options' command. +func OptionsHelpTemplate() string { + return "" +} + +// OptionsUsageTemplate if the template for 'usage' used by the 'options' command. +func OptionsUsageTemplate() string { + return `{{ if .HasInheritedFlags}}The following options can be passed to any command: {{flagsUsages .InheritedFlags}}{{end}}` -) +} From 3f7579cacb9902f44f1e594a54f2f8b441b2c550 Mon Sep 17 00:00:00 2001 From: Fabiano Franz Date: Fri, 7 Oct 2016 21:37:35 -0300 Subject: [PATCH 6/7] Tools for checking CLI conventions --- cmd/clicheck/check_cli_conventions.go | 48 ++++++++++++ hack/.linted_packages | 1 + hack/verify-cli-conventions.sh | 40 ++++++++++ pkg/kubectl/cmd/util/sanity/cmd_sanity.go | 94 +++++++++++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 cmd/clicheck/check_cli_conventions.go create mode 100755 hack/verify-cli-conventions.sh create mode 100644 pkg/kubectl/cmd/util/sanity/cmd_sanity.go diff --git a/cmd/clicheck/check_cli_conventions.go b/cmd/clicheck/check_cli_conventions.go new file mode 100644 index 00000000000..6c264b87b5d --- /dev/null +++ b/cmd/clicheck/check_cli_conventions.go @@ -0,0 +1,48 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "io/ioutil" + "os" + + "k8s.io/kubernetes/pkg/kubectl/cmd" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + cmdsanity "k8s.io/kubernetes/pkg/kubectl/cmd/util/sanity" +) + +var ( + skip = []string{} +) + +func main() { + errors := []error{} + + kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) + result := cmdsanity.CheckCmdTree(kubectl, cmdsanity.AllCmdChecks, []string{}) + errors = append(errors, result...) + + if len(errors) > 0 { + for i, err := range errors { + fmt.Fprintf(os.Stderr, "%d. %s\n\n", i+1, err) + } + os.Exit(1) + } + + fmt.Fprintln(os.Stdout, "Congrats, CLI looks good!") +} diff --git a/hack/.linted_packages b/hack/.linted_packages index 87545c341f2..2664e7a8417 100644 --- a/hack/.linted_packages +++ b/hack/.linted_packages @@ -1,6 +1,7 @@ cluster/addons/fluentd-elasticsearch/es-image cluster/images/etcd/attachlease cluster/images/etcd/rollback +cmd/clicheck cmd/gendocs cmd/genkubedocs cmd/genman diff --git a/hack/verify-cli-conventions.sh b/hack/verify-cli-conventions.sh new file mode 100755 index 00000000000..28a1a5c9be2 --- /dev/null +++ b/hack/verify-cli-conventions.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# Copyright 2016 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. +source "${KUBE_ROOT}/hack/lib/init.sh" + +kube::golang::setup_env + +BINS=( + cmd/clicheck +) +make -C "${KUBE_ROOT}" WHAT="${BINS[*]}" + +clicheck=$(kube::util::find-binary "clicheck") + +if ! output=`$clicheck 2>&1` +then + echo "FAILURE: CLI is not following one or more required conventions:" + echo "$output" + exit 1 +else + echo "SUCCESS: CLI is following all tested conventions." +fi diff --git a/pkg/kubectl/cmd/util/sanity/cmd_sanity.go b/pkg/kubectl/cmd/util/sanity/cmd_sanity.go new file mode 100644 index 00000000000..1460d3228e8 --- /dev/null +++ b/pkg/kubectl/cmd/util/sanity/cmd_sanity.go @@ -0,0 +1,94 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sanity + +import ( + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" +) + +type CmdCheck func(cmd *cobra.Command) []error + +var ( + AllCmdChecks = []CmdCheck{ + CheckLongDesc, + CheckExamples, + } +) + +func CheckCmdTree(cmd *cobra.Command, checks []CmdCheck, skip []string) []error { + cmdPath := cmd.CommandPath() + + for _, skipCmdPath := range skip { + if cmdPath == skipCmdPath { + fmt.Fprintf(os.Stdout, "-----+ skipping command %s\n", cmdPath) + return []error{} + } + } + + errors := []error{} + + if cmd.HasSubCommands() { + for _, subCmd := range cmd.Commands() { + errors = append(errors, CheckCmdTree(subCmd, checks, skip)...) + } + } + + fmt.Fprintf(os.Stdout, "-----+ checking command %s\n", cmdPath) + + for _, check := range checks { + if err := check(cmd); err != nil && len(err) > 0 { + errors = append(errors, err...) + } + } + + return errors +} + +func CheckLongDesc(cmd *cobra.Command) []error { + cmdPath := cmd.CommandPath() + long := cmd.Long + if len(long) > 0 { + if strings.Trim(long, " \t\n") != long { + return []error{fmt.Errorf(`command %q: long description is not normalized + ↳ make sure you are calling templates.LongDesc (from pkg/cmd/templates) before assigning cmd.Long`, cmdPath)} + } + } + return nil +} + +func CheckExamples(cmd *cobra.Command) []error { + cmdPath := cmd.CommandPath() + examples := cmd.Example + errors := []error{} + if len(examples) > 0 { + for _, line := range strings.Split(examples, "\n") { + if !strings.HasPrefix(line, templates.Indentation) { + errors = append(errors, fmt.Errorf(`command %q: examples are not normalized + ↳ make sure you are calling templates.Examples (from pkg/cmd/templates) before assigning cmd.Example`, cmdPath)) + } + if trimmed := strings.TrimSpace(line); strings.HasPrefix(trimmed, "//") { + errors = append(errors, fmt.Errorf(`command %q: we use # to start comments in examples instead of //`, cmdPath)) + } + } + } + return errors +} From 0fd5c8d7979096666a5c6a5e2f84da3af8faf571 Mon Sep 17 00:00:00 2001 From: Fabiano Franz Date: Mon, 10 Oct 2016 19:07:07 -0300 Subject: [PATCH 7/7] Use our own normalizers in the conventions doc --- docs/devel/kubectl-conventions.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/devel/kubectl-conventions.md b/docs/devel/kubectl-conventions.md index fe2e51a195d..dd388a61f29 100644 --- a/docs/devel/kubectl-conventions.md +++ b/docs/devel/kubectl-conventions.md @@ -301,24 +301,25 @@ Sample command skeleton: // MineRecommendedName is the recommended command name for kubectl mine. const MineRecommendedName = "mine" +// Long command description and examples. +var ( + mineLong = templates.LongDesc(` + mine which is described here + with lots of details.`) + + mineExample = templates.Examples(` + # Run my command's first action + kubectl mine first_action + + # Run my command's second action on latest stuff + kubectl mine second_action --flag`) +) + // MineConfig contains all the options for running the mine cli command. type MineConfig struct { mineLatest bool } -var ( - mineLong = dedent.Dedent(` - mine which is described here - with lots of details.`) - - mineExample = dedent.Dedent(` - # Run my command's first action - kubectl mine first_action - - # Run my command's second action on latest stuff - kubectl mine second_action --flag`) -) - // NewCmdMine implements the kubectl mine command. func NewCmdMine(parent, name string, f *cmdutil.Factory, out io.Writer) *cobra.Command { opts := &MineConfig{}