From 73713ce268c10a67707580d79c7b94a50f4bd6ac Mon Sep 17 00:00:00 2001 From: kargakis Date: Fri, 23 Oct 2015 17:31:03 +0200 Subject: [PATCH] edit: Windows fixes Contains the following fixes for Windows users of kubectl edit: * Defaults to notepad as the default Windows editor * Uses CRLF line endings * Ensures a file lock is freed --- contrib/completions/bash/kubectl | 1 + docs/man/man1/kubectl-edit.1 | 15 ++++--- docs/user-guide/kubectl/kubectl_edit.md | 14 +++--- hack/test-cmd.sh | 4 ++ hack/verify-flags/known-flags.txt | 1 + pkg/kubectl/cmd/edit.go | 25 +++++++---- pkg/kubectl/cmd/util/editor/editor.go | 25 +++++++++-- pkg/util/crlf.go | 57 +++++++++++++++++++++++++ 8 files changed, 120 insertions(+), 22 deletions(-) create mode 100644 pkg/util/crlf.go diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index 1c7d51741d6..84d553b8d48 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -498,6 +498,7 @@ _kubectl_edit() flags+=("--output=") two_word_flags+=("-o") flags+=("--output-version=") + flags+=("--windows-line-endings") must_have_one_flag=() must_have_one_noun=() diff --git a/docs/man/man1/kubectl-edit.1 b/docs/man/man1/kubectl-edit.1 index 0f6f8377dd2..50ac32251a9 100644 --- a/docs/man/man1/kubectl-edit.1 +++ b/docs/man/man1/kubectl-edit.1 @@ -18,15 +18,16 @@ Edit a resource from the default editor. .PP The edit command allows you to directly edit any API resource you can retrieve via the command line tools. It will open the editor defined by your KUBE\_EDITOR, GIT\_EDITOR, -or EDITOR environment variables, or fall back to 'vi'. You can edit multiple objects, -although changes are applied one at a time. The command accepts filenames as well as -command line arguments, although the files you point to must be previously saved -versions of resources. +or EDITOR environment variables, or fall back to 'vi' for Linux or 'notepad' for Windows. +You can edit multiple objects, although changes are applied one at a time. The command +accepts filenames as well as command line arguments, although the files you point to must +be previously saved versions of resources. .PP The files to edit will be output in the default API version, or a version specified by \-\-output\-version. The default format is YAML \- if you would like to edit in JSON -pass \-o json. +pass \-o json. The flag \-\-windows\-line\-endings can be used to force Windows line endings, +otherwise the default for your operating system will be used. .PP In the event an error occurs while updating, a temporary file will be created on disk @@ -49,6 +50,10 @@ saved copy to include the latest resource version. \fB\-\-output\-version\fP="" Output the formatted object with the given version (default api\-version). +.PP +\fB\-\-windows\-line\-endings\fP=false + Use Windows line\-endings (default Unix line\-endings) + .SH OPTIONS INHERITED FROM PARENT COMMANDS .PP diff --git a/docs/user-guide/kubectl/kubectl_edit.md b/docs/user-guide/kubectl/kubectl_edit.md index 8d34afb4855..dcfebe37adb 100644 --- a/docs/user-guide/kubectl/kubectl_edit.md +++ b/docs/user-guide/kubectl/kubectl_edit.md @@ -42,14 +42,15 @@ Edit a resource from the default editor. The edit command allows you to directly edit any API resource you can retrieve via the command line tools. It will open the editor defined by your KUBE_EDITOR, GIT_EDITOR, -or EDITOR environment variables, or fall back to 'vi'. You can edit multiple objects, -although changes are applied one at a time. The command accepts filenames as well as -command line arguments, although the files you point to must be previously saved -versions of resources. +or EDITOR environment variables, or fall back to 'vi' for Linux or 'notepad' for Windows. +You can edit multiple objects, although changes are applied one at a time. The command +accepts filenames as well as command line arguments, although the files you point to must +be previously saved versions of resources. The files to edit will be output in the default API version, or a version specified by --output-version. The default format is YAML - if you would like to edit in JSON -pass -o json. +pass -o json. The flag --windows-line-endings can be used to force Windows line endings, +otherwise the default for your operating system will be used. In the event an error occurs while updating, a temporary file will be created on disk that contains your unapplied changes. The most common error when updating a resource @@ -80,6 +81,7 @@ kubectl edit (RESOURCE/NAME | -f FILENAME) -f, --filename=[]: Filename, directory, or URL to file to use to edit the resource -o, --output="yaml": Output format. One of: yaml|json. --output-version="": Output the formatted object with the given version (default api-version). + --windows-line-endings[=false]: Use Windows line-endings (default Unix line-endings) ``` ### Options inherited from parent commands @@ -114,7 +116,7 @@ kubectl edit (RESOURCE/NAME | -f FILENAME) * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-09-22 12:53:42.290401559 +0000 UTC +###### Auto generated by spf13/cobra on 23-Oct-2015 [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_edit.md?pixel)]() diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index 4646b69f915..09379481fe9 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -485,6 +485,10 @@ runTests() { kube::test::get_object_assert pods "{{range.items}}{{$image_field}}:{{end}}" 'gcr.io/google_containers/serve_hostname:' # cleaning rm /tmp/tmp-editor.sh + [ "$(EDITOR=cat kubectl edit pod/valid-pod 2>&1 | grep 'Edit cancelled')" ] + [ "$(EDITOR=cat kubectl edit pod/valid-pod | grep 'name: valid-pod')" ] + [ "$(EDITOR=cat kubectl edit --windows-line-endings pod/valid-pod | file - | grep CRLF)" ] + [ ! "$(EDITOR=cat kubectl edit --windows-line-endings=false pod/valid-pod | file - | grep CRLF)" ] ### Overwriting an existing label is not permitted # Pre-condition: name is valid-pod diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index d3696c3d6ad..2f73fab35ab 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -304,6 +304,7 @@ user-whitelist watch-cache watch-only whitelist-override-label +windows-line-endings www-prefix retry_time file_content_in_loop diff --git a/pkg/kubectl/cmd/edit.go b/pkg/kubectl/cmd/edit.go index 1f1e2521ffa..f5a9467be23 100644 --- a/pkg/kubectl/cmd/edit.go +++ b/pkg/kubectl/cmd/edit.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "os" + "runtime" "strings" "k8s.io/kubernetes/pkg/api" @@ -33,6 +34,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl/cmd/util/editor" "k8s.io/kubernetes/pkg/kubectl/cmd/util/jsonmerge" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util/strategicpatch" "k8s.io/kubernetes/pkg/util/yaml" @@ -45,14 +47,15 @@ const ( The edit command allows you to directly edit any API resource you can retrieve via the command line tools. It will open the editor defined by your KUBE_EDITOR, GIT_EDITOR, -or EDITOR environment variables, or fall back to 'vi'. You can edit multiple objects, -although changes are applied one at a time. The command accepts filenames as well as -command line arguments, although the files you point to must be previously saved -versions of resources. +or EDITOR environment variables, or fall back to 'vi' for Linux or 'notepad' for Windows. +You can edit multiple objects, although changes are applied one at a time. The command +accepts filenames as well as command line arguments, although the files you point to must +be previously saved versions of resources. The files to edit will be output in the default API version, or a version specified by --output-version. The default format is YAML - if you would like to edit in JSON -pass -o json. +pass -o json. The flag --windows-line-endings can be used to force Windows line endings, +otherwise the default for your operating system will be used. In the event an error occurs while updating, a temporary file will be created on disk that contains your unapplied changes. The most common error when updating a resource @@ -91,6 +94,7 @@ func NewCmdEdit(f *cmdutil.Factory, out io.Writer) *cobra.Command { kubectl.AddJsonFilenameFlag(cmd, &filenames, usage) cmd.Flags().StringP("output", "o", "yaml", "Output format. One of: yaml|json.") cmd.Flags().String("output-version", "", "Output the formatted object with the given version (default api-version).") + cmd.Flags().Bool("windows-line-endings", runtime.GOOS == "windows", "Use Windows line-endings (default Unix line-endings)") return cmd } @@ -142,6 +146,8 @@ func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin return err } + windowsLineEndings := cmdutil.GetFlagBool(cmd, "windows-line-endings") + edit := editor.NewDefaultEditor() defaultVersion := cmdutil.OutputVersion(cmd, clientConfig.Version) results := editResults{} for { @@ -156,16 +162,19 @@ func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin // generate the file to edit buf := &bytes.Buffer{} - if err := results.header.writeTo(buf); err != nil { + var w io.Writer = buf + if windowsLineEndings { + w = util.NewCRLFWriter(w) + } + if err := results.header.writeTo(w); err != nil { return preservedFile(err, results.file, out) } - if err := printer.PrintObj(obj, buf); err != nil { + if err := printer.PrintObj(obj, w); err != nil { return preservedFile(err, results.file, out) } original := buf.Bytes() // launch the editor - edit := editor.NewDefaultEditor() edited, file, err := edit.LaunchTempFile("kubectl-edit-", ext, buf) if err != nil { return preservedFile(err, results.file, out) diff --git a/pkg/kubectl/cmd/util/editor/editor.go b/pkg/kubectl/cmd/util/editor/editor.go index 2145792e901..8bf7450f10a 100644 --- a/pkg/kubectl/cmd/util/editor/editor.go +++ b/pkg/kubectl/cmd/util/editor/editor.go @@ -25,6 +25,7 @@ import ( "os/exec" "os/signal" "path/filepath" + "runtime" "strings" "github.com/docker/docker/pkg/term" @@ -33,8 +34,13 @@ import ( const ( // sorry, blame Git + // TODO: on Windows rely on 'start' to launch the editor associated + // with the given file type. If we can't because of the need of + // blocking, use a script with 'ftype' and 'assoc' to detect it. defaultEditor = "vi" defaultShell = "/bin/bash" + windowsEditor = "notepad" + windowsShell = "cmd" ) type Editor struct { @@ -58,15 +64,19 @@ func NewDefaultEditor() Editor { func defaultEnvShell() []string { shell := os.Getenv("SHELL") if len(shell) == 0 { - shell = defaultShell + shell = platformize(defaultShell, windowsShell) } - return []string{shell, "-c"} + flag := "-c" + if shell == windowsShell { + flag = "/C" + } + return []string{shell, flag} } func defaultEnvEditor() ([]string, bool) { editor := os.Getenv("EDITOR") if len(editor) == 0 { - editor = defaultEditor + editor = platformize(defaultEditor, windowsEditor) } if !strings.Contains(editor, " ") { return []string{editor}, false @@ -133,6 +143,8 @@ func (e Editor) LaunchTempFile(prefix, suffix string, r io.Reader) ([]byte, stri os.Remove(path) return nil, path, err } + // This file descriptor needs to close so the next process (Launch) can claim it. + f.Close() if err := e.Launch(path); err != nil { return nil, path, err } @@ -197,3 +209,10 @@ func randSeq(n int) string { } return string(b) } + +func platformize(linux, windows string) string { + if runtime.GOOS == "windows" { + return windows + } + return linux +} diff --git a/pkg/util/crlf.go b/pkg/util/crlf.go new file mode 100644 index 00000000000..935785ce974 --- /dev/null +++ b/pkg/util/crlf.go @@ -0,0 +1,57 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 util + +import ( + "bytes" + "io" +) + +type crlfWriter struct { + io.Writer +} + +// NewCRLFWriter implements a CR/LF line ending writer used for normalizing +// text for Windows platforms. +func NewCRLFWriter(w io.Writer) io.Writer { + return crlfWriter{w} +} + +func (w crlfWriter) Write(b []byte) (n int, err error) { + for i, written := 0, 0; ; { + next := bytes.Index(b[i:], []byte("\n")) + if next == -1 { + n, err := w.Writer.Write(b[i:]) + return written + n, err + } + next = next + i + n, err := w.Writer.Write(b[i:next]) + if err != nil { + return written + n, err + } + written += n + n, err = w.Writer.Write([]byte("\r\n")) + if err != nil { + if n > 1 { + n = 1 + } + return written + n, err + } + written += 1 + i = next + 1 + } +}