/
container.go
208 lines (191 loc) · 6.38 KB
/
container.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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package container
import (
"fmt"
"os"
"path/filepath"
"sigs.k8s.io/kustomize/kyaml/errors"
runtimeexec "sigs.k8s.io/kustomize/kyaml/fn/runtime/exec"
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Filter filters Resources using a container image.
// The container must start a process that reads the list of
// input Resources from stdin, reads the Configuration from the env
// API_CONFIG, and writes the filtered Resources to stdout.
// If there is a error or validation failure, the process must exit
// non-zero.
// The full set of environment variables from the parent process
// are passed to the container.
//
// Function Scoping:
// Filter applies the function only to Resources to which it is scoped.
//
// Resources are scoped to a function if any of the following are true:
// - the Resource were read from the same directory as the function config
// - the Resource were read from a subdirectory of the function config directory
// - the function config is in a directory named "functions" and
// they were read from a subdirectory of "functions" parent
// - the function config doesn't have a path annotation (considered globally scoped)
// - the Filter has GlobalScope == true
//
// In Scope Examples:
//
// Example 1: deployment.yaml and service.yaml in function.yaml scope
// same directory as the function config directory
// .
// ├── function.yaml
// ├── deployment.yaml
// └── service.yaml
//
// Example 2: apps/deployment.yaml and apps/service.yaml in function.yaml scope
// subdirectory of the function config directory
// .
// ├── function.yaml
// └── apps
// ├── deployment.yaml
// └── service.yaml
//
// Example 3: apps/deployment.yaml and apps/service.yaml in functions/function.yaml scope
// function config is in a directory named "functions"
// .
// ├── functions
// │ └── function.yaml
// └── apps
// ├── deployment.yaml
// └── service.yaml
//
// Out of Scope Examples:
//
// Example 1: apps/deployment.yaml and apps/service.yaml NOT in stuff/function.yaml scope
// .
// ├── stuff
// │ └── function.yaml
// └── apps
// ├── deployment.yaml
// └── service.yaml
//
// Example 2: apps/deployment.yaml and apps/service.yaml NOT in stuff/functions/function.yaml scope
// .
// ├── stuff
// │ └── functions
// │ └── function.yaml
// └── apps
// ├── deployment.yaml
// └── service.yaml
//
// Default Paths:
// Resources emitted by functions will have default path applied as annotations
// if none is present.
// The default path will be the function-dir/ (or parent directory in the case of "functions")
// + function-file-name/ + namespace/ + kind_name.yaml
//
// Example 1: Given a function in fn.yaml that produces a Deployment name foo and a Service named bar
// dir
// └── fn.yaml
//
// Would default newly generated Resources to:
//
// dir
// ├── fn.yaml
// └── fn
// ├── deployment_foo.yaml
// └── service_bar.yaml
//
// Example 2: Given a function in functions/fn.yaml that produces a Deployment name foo and a Service named bar
// dir
// └── fn.yaml
//
// Would default newly generated Resources to:
//
// dir
// ├── functions
// │ └── fn.yaml
// └── fn
// ├── deployment_foo.yaml
// └── service_bar.yaml
//
// Example 3: Given a function in fn.yaml that produces a Deployment name foo, namespace baz and a Service named bar namespace baz
// dir
// └── fn.yaml
//
// Would default newly generated Resources to:
//
// dir
// ├── fn.yaml
// └── fn
// └── baz
// ├── deployment_foo.yaml
// └── service_bar.yaml
type Filter struct {
runtimeutil.ContainerSpec `json:",inline" yaml:",inline"`
Exec runtimeexec.Filter
UIDGID string
}
func (c Filter) String() string {
if c.Exec.DeferFailure {
return fmt.Sprintf("%s deferFailure: %v", c.Image, c.Exec.DeferFailure)
}
return c.Image
}
func (c Filter) GetExit() error {
return c.Exec.GetExit()
}
func (c *Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
if err := c.setupExec(); err != nil {
return nil, err
}
return c.Exec.Filter(nodes)
}
func (c *Filter) setupExec() error {
// don't init 2x
if c.Exec.Path != "" {
return nil
}
if c.Exec.WorkingDir == "" {
wd, err := os.Getwd()
if err != nil {
return errors.Wrap(err)
}
c.Exec.WorkingDir = wd
}
path, args := c.getCommand()
c.Exec.Path = path
c.Exec.Args = args
return nil
}
// getArgs returns the command + args to run to spawn the container
func (c *Filter) getCommand() (string, []string) {
network := runtimeutil.NetworkNameNone
if c.ContainerSpec.Network {
network = runtimeutil.NetworkNameHost
}
// run the container using docker. this is simpler than using the docker
// libraries, and ensures things like auth work the same as if the container
// was run from the cli.
args := []string{"run",
"--rm", // delete the container afterward
"-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR", // attach stdin, stdout, stderr
"--network", string(network),
// added security options
"--user", c.UIDGID,
"--security-opt=no-new-privileges", // don't allow the user to escalate privileges
// note: don't make fs readonly because things like heredoc rely on writing tmp files
}
for _, storageMount := range c.StorageMounts {
// convert declarative relative paths to absolute (otherwise docker will throw an error)
if !filepath.IsAbs(storageMount.Src) {
storageMount.Src = filepath.Join(c.Exec.WorkingDir, storageMount.Src)
}
args = append(args, "--mount", storageMount.String())
}
args = append(args, runtimeutil.NewContainerEnvFromStringSlice(c.Env).GetDockerFlags()...)
a := append(args, c.Image) //nolint:gocritic
return "docker", a
}
// NewContainer returns a new container filter
func NewContainer(spec runtimeutil.ContainerSpec, uidgid string) Filter {
f := Filter{ContainerSpec: spec, UIDGID: uidgid}
return f
}