Add support for json output in inject and uninject commands (#12600)

We add support for the `--output/-o` flag in `linkerd inject` and `linkerd uninject` commands.  The supported output formats are yaml (default) and json.  Kubectl is able to accept both of these formats which means that `linkerd inject` and `linkerd uninject` output can be piped into kubectl regardless of which output format is used.

Signed-off-by: Alex Leong <alex@buoyant.io>
This commit is contained in:
Alex Leong 2024-05-21 16:36:53 -07:00 committed by GitHub
parent 46c315034b
commit 10b1a7af6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 44 additions and 20 deletions

View File

@ -42,8 +42,8 @@ type resourceTransformerInject struct {
closeWaitTimeout time.Duration
}
func runInjectCmd(inputs []io.Reader, errWriter, outWriter io.Writer, transformer *resourceTransformerInject) int {
return transformInput(inputs, errWriter, outWriter, transformer)
func runInjectCmd(inputs []io.Reader, errWriter, outWriter io.Writer, transformer *resourceTransformerInject, output string) int {
return transformInput(inputs, errWriter, outWriter, transformer, output)
}
func newCmdInject() *cobra.Command {
@ -56,6 +56,7 @@ func newCmdInject() *cobra.Command {
injectFlags, injectFlagSet := makeInjectFlags(defaults)
var manualOption, enableDebugSidecar bool
var closeWaitTimeout time.Duration
var output string
cmd := &cobra.Command{
Use: "inject [flags] CONFIG-FILE",
@ -109,7 +110,7 @@ sub-folders, or coming from stdin.`,
enableDebugSidecar: enableDebugSidecar,
closeWaitTimeout: closeWaitTimeout,
}
exitCode := uninjectAndInject(in, stderr, stdout, transformer)
exitCode := uninjectAndInject(in, stderr, stdout, transformer, output)
os.Exit(exitCode)
return nil
},
@ -132,18 +133,20 @@ sub-folders, or coming from stdin.`,
&closeWaitTimeout, "close-wait-timeout", closeWaitTimeout,
"Sets nf_conntrack_tcp_timeout_close_wait")
cmd.Flags().StringVarP(&output, "output", "o", "yaml", "Output format, one of: json|yaml")
cmd.Flags().AddFlagSet(proxyFlagSet)
cmd.Flags().AddFlagSet(injectFlagSet)
return cmd
}
func uninjectAndInject(inputs []io.Reader, errWriter, outWriter io.Writer, transformer *resourceTransformerInject) int {
func uninjectAndInject(inputs []io.Reader, errWriter, outWriter io.Writer, transformer *resourceTransformerInject, output string) int {
var out bytes.Buffer
if exitCode := runUninjectSilentCmd(inputs, errWriter, &out, transformer.values); exitCode != 0 {
if exitCode := runUninjectSilentCmd(inputs, errWriter, &out, transformer.values, "yaml"); exitCode != 0 {
return exitCode
}
return runInjectCmd([]io.Reader{&out}, errWriter, outWriter, transformer)
return runInjectCmd([]io.Reader{&out}, errWriter, outWriter, transformer, output)
}
func (rt resourceTransformerInject) transform(bytes []byte) ([]byte, []inject.Report, error) {

View File

@ -52,7 +52,7 @@ func testUninjectAndInject(t *testing.T, tc testCase) {
allowNsInject: true,
}
if exitCode := uninjectAndInject([]io.Reader{read}, report, output, transformer); exitCode != 0 {
if exitCode := uninjectAndInject([]io.Reader{read}, report, output, transformer, "yaml"); exitCode != 0 {
t.Errorf("Unexpected error injecting YAML: %v", report)
}
if err := testDataDiffer.DiffTestYAML(tc.goldenFileName, output.String()); err != nil {
@ -432,7 +432,7 @@ func testInjectCmd(t *testing.T, tc injectCmd) {
injectProxy: tc.injectProxy,
values: testConfig,
}
exitCode := runInjectCmd([]io.Reader{in}, errBuffer, outBuffer, transformer)
exitCode := runInjectCmd([]io.Reader{in}, errBuffer, outBuffer, transformer, "yaml")
if exitCode != tc.exitCode {
t.Fatalf("Expected exit code to be %d but got: %d", tc.exitCode, exitCode)
}
@ -538,7 +538,7 @@ func testInjectFilePath(t *testing.T, tc injectFilePath) {
injectProxy: true,
values: values,
}
if exitCode := runInjectCmd(in, errBuf, actual, transformer); exitCode != 0 {
if exitCode := runInjectCmd(in, errBuf, actual, transformer, "yaml"); exitCode != 0 {
t.Fatal("Unexpected error. Exit code from runInjectCmd: ", exitCode)
}
if err := testDataDiffer.DiffTestYAML(tc.expectedFile, actual.String()); err != nil {
@ -565,7 +565,7 @@ func testReadFromFolder(t *testing.T, resourceFolder string, expectedFolder stri
injectProxy: true,
values: values,
}
if exitCode := runInjectCmd(in, errBuf, actual, transformer); exitCode != 0 {
if exitCode := runInjectCmd(in, errBuf, actual, transformer, "yaml"); exitCode != 0 {
t.Fatal("Unexpected error. Exit code from runInjectCmd: ", exitCode)
}

View File

@ -26,12 +26,12 @@ type resourceTransformer interface {
}
// Returns the integer representation of os.Exit code; 0 on success and 1 on failure.
func transformInput(inputs []io.Reader, errWriter, outWriter io.Writer, rt resourceTransformer) int {
func transformInput(inputs []io.Reader, errWriter, outWriter io.Writer, rt resourceTransformer, format string) int {
postInjectBuf := &bytes.Buffer{}
reportBuf := &bytes.Buffer{}
for _, input := range inputs {
errs := processYAML(input, postInjectBuf, reportBuf, rt)
errs := processYAML(input, postInjectBuf, reportBuf, rt, format)
if len(errs) > 0 {
fmt.Fprintf(errWriter, "Error transforming resources:\n%v", concatErrors(errs, "\n"))
return 1
@ -51,7 +51,7 @@ func transformInput(inputs []io.Reader, errWriter, outWriter io.Writer, rt resou
}
// processYAML takes an input stream of YAML, outputting injected/uninjected YAML to out.
func processYAML(in io.Reader, out io.Writer, report io.Writer, rt resourceTransformer) []error {
func processYAML(in io.Reader, out io.Writer, report io.Writer, rt resourceTransformer, format string) []error {
reader := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(in, 4096))
reports := []inject.Report{}
@ -86,9 +86,26 @@ func processYAML(in io.Reader, out io.Writer, report io.Writer, rt resourceTrans
}
reports = append(reports, irs...)
// If the format is set to json, we need to convert the yaml to json
if format == "json" {
result, err = yaml.YAMLToJSON(result)
if err != nil {
errs = append(errs, err)
}
} else if format == "yaml" {
// result is already in yaml format: noop.
} else {
errs = append(errs, fmt.Errorf("unsupported format %s", format))
}
if len(errs) == 0 {
out.Write(result)
out.Write([]byte("---\n"))
if format == "yaml" {
out.Write([]byte("---\n"))
}
if format == "json" {
out.Write([]byte("\n"))
}
}
}

View File

@ -18,15 +18,17 @@ type resourceTransformerUninjectSilent struct {
values *linkerd2.Values
}
func runUninjectCmd(inputs []io.Reader, errWriter, outWriter io.Writer, values *linkerd2.Values) int {
return transformInput(inputs, errWriter, outWriter, resourceTransformerUninject{values})
func runUninjectCmd(inputs []io.Reader, errWriter, outWriter io.Writer, values *linkerd2.Values, output string) int {
return transformInput(inputs, errWriter, outWriter, resourceTransformerUninject{values}, output)
}
func runUninjectSilentCmd(inputs []io.Reader, errWriter, outWriter io.Writer, values *linkerd2.Values) int {
return transformInput(inputs, errWriter, outWriter, resourceTransformerUninjectSilent{values})
func runUninjectSilentCmd(inputs []io.Reader, errWriter, outWriter io.Writer, values *linkerd2.Values, output string) int {
return transformInput(inputs, errWriter, outWriter, resourceTransformerUninjectSilent{values}, output)
}
func newCmdUninject() *cobra.Command {
var output string
cmd := &cobra.Command{
Use: "uninject [flags] CONFIG-FILE",
Short: "Remove the Linkerd proxy from a Kubernetes config",
@ -53,12 +55,14 @@ sub-folders, or coming from stdin.`,
return err
}
exitCode := runUninjectCmd(in, os.Stderr, os.Stdout, nil)
exitCode := runUninjectCmd(in, os.Stderr, os.Stdout, nil, output)
os.Exit(exitCode)
return nil
},
}
cmd.Flags().StringVarP(&output, "output", "o", "yaml", "Output format, one of: json|yaml")
return cmd
}

View File

@ -116,7 +116,7 @@ func TestUninjectYAML(t *testing.T) {
output := new(bytes.Buffer)
report := new(bytes.Buffer)
exitCode := runUninjectCmd(read, report, output, values)
exitCode := runUninjectCmd(read, report, output, values, "yaml")
if exitCode != 0 {
t.Errorf("Failed to uninject %s", tc.inputFileName)
}