【问题标题】:Inside kubectl plugin, prompt for input?在 kubectl 插件中,提示输入?
【发布时间】:2019-11-09 18:56:43
【问题描述】:

我正在编写一个kubectl 插件来验证用户,我想在插件被调用后提示用户输入密码。据我了解,从 STDIN 获取输入相当简单,但我很难看到写入 STDOUT 的消息。目前我的代码如下所示:

在 cmd/kubectl-myauth.go 中:

// This is mostly boilerplate, but it's needed for the MRE
// https://stackoverflow.com/help/minimal-reproducible-example
package myauth
import (...)
func main() {
    pflag.CommandLine = pflag.NewFlagSet("kubectl-myauth", pflag.ExitOnError)
    root := cmd.NewCmdAuthOp(genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr})
    if err := root.Execute(); err != nil {
        os.Exit(1)
    }
}

在 pkg/cmd/auth.go 中:

package cmd
...
type AuthOpOptions struct {
    configFlags *genericclioptions.ConfigFlags
    resultingContext *api.Context
    rawConfig       api.Config
    args            []string
    ...
    genericclioptions.IOStreams
}
func NewAuthOpOptions(streams genericclioptions.IOStreams) *AuthOpOptions {
    return &AuthOpOptions{
        configFlags: genericclioptions.NewConfigFlags(true),
        IOStreams: streams,
    }
}
func NewCmdAuthOp(streams genericclioptions.IOStreams) *cobra.Command {
    o := NewAuthOpOptions(streams)
    cmd := &cobra.Command{
        RunE: func(c *cobra.Command, args []string) error {
            return o.Run()
        },
    }
    return cmd
}
func (o *AuthOpOptions) Run() error {
    pass, err := getPassword(o)
    if err != nil {
        return err
    }
    // Do Auth Stuff
    // Eventually print an ExecCredential to STDOUT
    return nil
}
func getPassword(o *AuthOpOptions) (string, error) {
    var reader *bufio.Reader
    reader = nil
    pass := ""
    for pass == "" {
        // THIS IS AN IMPORTANT LINE [1]
        fmt.Fprintf(o.IOStreams.Out, "Password with which to authenticate:\n")
        // THE REST OF THIS IS STILL IMPORTANT, BUT LESS SO [2]
        if reader == nil {
            // The first time through, initialize the reader
            reader = bufio.NewReader(o.IOStreams.In)
        }
        pass, err := reader.ReadString('\n')
        if err != nil {
            return nil, err
        }
        pass = strings.Trim(pass, "\r\n")
        if pass == "" {
            // ALSO THIS LINE IS IMPORTANT [3]
            fmt.Fprintf(o.IOStreams.Out, `Read password was empty string.
Please input a valid password.
`)
        }
    }
    return pass, nil
}

这符合我在kubectl 上下文之外运行时所期望的方式——即打印字符串、提示输入并继续。但是,在kubectl 上下文中,我相信前两个全大写 cmets([1] 和 [2])之间的打印被kubectl 在 STDOUT 上监听。我可以通过打印到 STDERR 来解决这个问题,但这感觉......错了。有没有办法绕过kubectl的STDOUT消耗与用户交流?

TL;DR:kubectl 似乎正在吞噬 kubectl 插件的所有 STDOUT,但我想提示用户输入 - 有没有简单的方法可以做到这一点?

【问题讨论】:

  • 我想指出,STDERR 在这里不一定被视为错误。考虑一下:您正在使用一种工具,用户可以在其中处理输出 (STDOUT)。他们可能将输出传送到脚本或从脚本传送。如果您的提示信息被发送到 STDOUT,它不仅会被脚本/管道吞噬,还可能导致下游脚本问题。当在 STDERR 上出现提示时,它允许用户查看它,输入所需的信息,并让命令在脚本/管道中继续,因为它通常在流中没有额外信息。
  • 我自称对“用户提示使用哪种输出机制”一无所知 - STDERR 在这里可能是正确的。这让我很好奇为什么许多命令行工具的设计者不提供四种输出机制 - 输出给用户(或上一级),错误输出给用户(或上一级),输出到我,对我犯错。这似乎更...开发人员友好?呃,业余PR目标。
  • @distortedsignal Andy 的评论是否令人满意,可以将其视为您问题的答案?
  • @OhHiMark - 不是吗? Andy 的回答是一个很好的解决方法,但是“要求输入密码是一个错误”并不是一个很好的答案(因为在我的示例中,它在预期的工作流程中)。这有意义吗?
  • @distortedsignal 谢谢!我已经重现了这个问题。我会试一试的。

标签: go kubernetes kubectl


【解决方案1】:

抱歉,我没有比“为我工作”更好的答案 :-) 以下是步骤:

  • git clone https://github.com/kubernetes/kubernetes.git

  • sample-cli-plugin 复制为test-cli-plugin(这涉及修复暂存/发布下的import-restrictions.yaml、rules-godeps.yaml 和rules.yaml - 可能没有必要,但这样更安全)

  • 将 kubectl-ns.go 更改为 kubectl-test.go:

package main

import (
        "os"

        "github.com/spf13/pflag"

        "k8s.io/cli-runtime/pkg/genericclioptions"
        "k8s.io/test-cli-plugin/pkg/cmd"
)

func main() {
        flags := pflag.NewFlagSet("kubectl-test", pflag.ExitOnError)
        pflag.CommandLine = flags

        root := cmd.NewCmdTest(genericclioptions.IOStreams{In: os.Stdin, 
                                                           Out: os.Stdout,
                                                           ErrOut: os.Stderr})
        if err := root.Execute(); err != nil {
                os.Exit(1)
        }
}
  • 将 ns.go 更改为 test.go:
package cmd

import (
        "fmt"
        "os"

        "github.com/spf13/cobra"

        "k8s.io/cli-runtime/pkg/genericclioptions"
)

type TestOptions struct {
        configFlags *genericclioptions.ConfigFlags
        genericclioptions.IOStreams
}

func NewTestOptions(streams genericclioptions.IOStreams) *TestOptions {
        return &TestOptions{
                configFlags: genericclioptions.NewConfigFlags(true),
                IOStreams:   streams,
        }
}

func NewCmdTest(streams genericclioptions.IOStreams) *cobra.Command {
        o := NewTestOptions(streams)

        cmd := &cobra.Command{
                Use:          "test",
                Short:        "Test plugin",
                SilenceUsage: true,
                RunE: func(c *cobra.Command, args []string) error {
                        o.Run()
                        return nil
                },
        }

        return cmd
}

func (o *TestOptions) Run() error {
        fmt.Fprintf(os.Stderr, "Testing Fprintf Stderr\n")
        fmt.Fprintf(os.Stdout, "Testing Fprintf Stdout\n")
        fmt.Printf("Testing Printf\n")
        fmt.Fprintf(o.IOStreams.Out, "Testing Fprintf o.IOStreams.Out\n")
        return nil
}
  • 相应地修复 BUILD 文件
  • 构建插件
  • 运行make
  • 复制kubectl-test到/usr/local/bin
  • 运行编译后的kubectl二进制文件:

~/k8s/_output/bin$ ./kubectl 测试

测试 Fprintf 标准错误

测试 Fprintf 标准输出

测试 Printf

测试 Fprintf o.IOStreams.Out

【讨论】:

  • @distortedsignal 抱歉 - 在第一个版本中,我需要验证这确实是 k8s 版本,而不是我之前尝试的 GKE 版本。我想从答案中删除它,因为它无关紧要,但忘记从代码中删除它。
猜你喜欢
  • 2015-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-20
  • 2013-01-25
  • 2018-02-16
  • 1970-01-01
相关资源
最近更新 更多