forked from kubernetes-sigs/kustomize
/
command.go
168 lines (150 loc) · 5.07 KB
/
command.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package command
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
type CLIMode byte
const (
StandaloneEnabled CLIMode = iota
StandaloneDisabled
)
// Build returns a cobra.Command to run a function.
//
// The cobra.Command reads the input from STDIN, invokes the provided processor,
// and then writes the output to STDOUT.
//
// The cobra.Command has a boolean `--stack` flag to print stack traces on failure.
//
// By default, invoking the returned cobra.Command with arguments triggers "standalone" mode.
// In this mode:
// - The first argument must be the name of a file containing the FunctionConfig.
// - The remaining arguments must be filenames containing input resources for ResourceList.Items.
// - The argument "-", if present, will cause resources to be read from STDIN as well.
// The output will be a raw stream of resources (not wrapped in a List type).
// Example usage: `cat input1.yaml | go run main.go config.yaml input2.yaml input3.yaml -`
//
// If mode is `StandaloneDisabled`, all arguments are ignored, and STDIN must contain
// a Kubernetes List type. To pass a function config in this mode, use a ResourceList as the input.
// The output will be of the same type as the input (e.g. ResourceList).
// Example usage: `cat resource_list.yaml | go run main.go`
//
// By default, any error returned by the ResourceListProcessor will be printed to STDERR.
// Set noPrintError to true to suppress this.
func Build(p framework.ResourceListProcessor, mode CLIMode, noPrintError bool) *cobra.Command {
cmd := cobra.Command{}
var printStack bool
cmd.Flags().BoolVar(&printStack, "stack", false, "print the stack trace on failure")
cmd.Args = cobra.MinimumNArgs(0)
cmd.SilenceErrors = true
cmd.SilenceUsage = true
cmd.RunE = func(cmd *cobra.Command, args []string) error {
var readers []io.Reader
rw := &kio.ByteReadWriter{
Writer: cmd.OutOrStdout(),
KeepReaderAnnotations: true,
}
if len(args) > 0 && mode == StandaloneEnabled {
// Don't keep the reader annotations if we are in standalone mode
rw.KeepReaderAnnotations = false
// Don't wrap the resources in a resourceList -- we are in
// standalone mode and writing to stdout to be applied
rw.NoWrap = true
for i := range args {
// the first argument is the resourceList.FunctionConfig
if i == 0 {
var err error
if rw.FunctionConfig, err = functionConfigFromFile(args[0]); err != nil {
return errors.Wrap(err)
}
continue
}
if args[i] == "-" {
readers = append([]io.Reader{cmd.InOrStdin()}, readers...)
} else {
readers = append(readers, &deferredFileReader{path: args[i]})
}
}
} else {
// legacy kustomize plugin input style
legacyPlugin := os.Getenv("KUSTOMIZE_PLUGIN_CONFIG_STRING")
if legacyPlugin != "" && rw.FunctionConfig != nil {
if err := yaml.Unmarshal([]byte(legacyPlugin), rw.FunctionConfig); err != nil {
return err
}
}
readers = append(readers, cmd.InOrStdin())
}
rw.Reader = io.MultiReader(readers...)
err := framework.Execute(p, rw)
if err != nil && !noPrintError {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%v", err)
}
// print the stack if requested
if s := errors.GetStack(err); printStack && s != "" {
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), s)
}
return err
}
return &cmd
}
// AddGenerateDockerfile adds a "gen" subcommand to create a Dockerfile for building
// the function into a container image.
// The gen command takes one argument: the directory where the Dockerfile will be created.
//
// go run main.go gen DIR/
func AddGenerateDockerfile(cmd *cobra.Command) {
gen := &cobra.Command{
Use: "gen [DIR]",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return ioutil.WriteFile(filepath.Join(args[0], "Dockerfile"), []byte(`FROM golang:1.16-alpine as builder
ENV CGO_ENABLED=0
WORKDIR /go/src/
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -ldflags '-w -s' -v -o /usr/local/bin/function ./
FROM alpine:latest
COPY --from=builder /usr/local/bin/function /usr/local/bin/function
ENTRYPOINT ["function"]
`), 0600)
},
}
cmd.AddCommand(gen)
}
func functionConfigFromFile(file string) (*yaml.RNode, error) {
b, err := ioutil.ReadFile(file)
if err != nil {
return nil, errors.WrapPrefixf(err, "unable to read configuration file %q", file)
}
fc, err := yaml.Parse(string(b))
if err != nil {
return nil, errors.WrapPrefixf(err, "unable to parse configuration file %q", file)
}
return fc, nil
}
type deferredFileReader struct {
path string
srcReader io.Reader
}
func (fr *deferredFileReader) Read(dest []byte) (int, error) {
if fr.srcReader == nil {
src, err := ioutil.ReadFile(fr.path)
if err != nil {
return 0, errors.WrapPrefixf(err, "unable to read input file %s", fr.path)
}
fr.srcReader = bytes.NewReader(src)
}
return fr.srcReader.Read(dest)
}