File: fnplugin.go

package info (click to toggle)
golang-k8s-sigs-kustomize-api 0.20.1%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,768 kB
  • sloc: makefile: 206; sh: 67
file content (201 lines) | stat: -rw-r--r-- 5,256 bytes parent folder | download | duplicates (2)
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
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package fnplugin

import (
	"bytes"
	"fmt"

	"sigs.k8s.io/kustomize/kyaml/errors"

	"sigs.k8s.io/kustomize/api/internal/plugins/utils"
	"sigs.k8s.io/kustomize/api/resmap"
	"sigs.k8s.io/kustomize/api/resource"
	"sigs.k8s.io/kustomize/api/types"
	"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
	"sigs.k8s.io/kustomize/kyaml/runfn"
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

// FnPlugin is the struct to hold function information
type FnPlugin struct {
	// Function runner
	runFns runfn.RunFns

	// Plugin configuration data.
	cfg []byte

	// Plugin name cache for error output
	pluginName string

	// PluginHelpers
	h *resmap.PluginHelpers
}

func bytesToRNode(yml []byte) (*yaml.RNode, error) {
	rnode, err := yaml.Parse(string(yml))
	if err != nil {
		return nil, err
	}
	return rnode, nil
}

func resourceToRNode(res *resource.Resource) (*yaml.RNode, error) {
	yml, err := res.AsYAML()
	if err != nil {
		return nil, err
	}

	return bytesToRNode(yml)
}

// GetFunctionSpec return function spec is there is. Otherwise return nil
func GetFunctionSpec(res *resource.Resource) (*runtimeutil.FunctionSpec, error) {
	rnode, err := resourceToRNode(res)
	if err != nil {
		return nil, fmt.Errorf("could not convert resource to RNode: %w", err)
	}
	functionSpec, err := runtimeutil.GetFunctionSpec(rnode)
	if err != nil {
		return nil, fmt.Errorf("failed to get FunctionSpec: %w", err)
	}
	return functionSpec, nil
}

func toStorageMounts(mounts []string) []runtimeutil.StorageMount {
	var sms []runtimeutil.StorageMount
	for _, mount := range mounts {
		sms = append(sms, runtimeutil.StringToStorageMount(mount))
	}
	return sms
}

// NewFnPlugin creates a FnPlugin struct
func NewFnPlugin(o *types.FnPluginLoadingOptions) *FnPlugin {
	return &FnPlugin{
		runFns: runfn.RunFns{
			Functions:      []*yaml.RNode{},
			Network:        o.Network,
			EnableExec:     o.EnableExec,
			StorageMounts:  toStorageMounts(o.Mounts),
			Env:            o.Env,
			AsCurrentUser:  o.AsCurrentUser,
			WorkingDir:     o.WorkingDir,
		},
	}
}

// Cfg returns function config
func (p *FnPlugin) Cfg() []byte {
	return p.cfg
}

// Config is called by kustomize to pass-in config information
func (p *FnPlugin) Config(h *resmap.PluginHelpers, config []byte) error {
	p.h = h
	p.cfg = config

	fn, err := bytesToRNode(p.cfg)
	if err != nil {
		return err
	}

	meta, err := fn.GetMeta()
	if err != nil {
		return err
	}

	p.pluginName = fmt.Sprintf("api: %s, kind: %s, name: %s",
		meta.APIVersion, meta.Kind, meta.Name)

	return nil
}

// Generate is called when run as generator
func (p *FnPlugin) Generate() (resmap.ResMap, error) {
	output, err := p.invokePlugin(nil)
	if err != nil {
		return nil, err
	}
	rm, err := p.h.ResmapFactory().NewResMapFromBytes(output)
	if err != nil {
		return nil, err
	}
	return utils.UpdateResourceOptions(rm)
}

// Transform is called when run as transformer
func (p *FnPlugin) Transform(rm resmap.ResMap) error {
	// add ResIds as annotations to all objects so that we can add them back
	inputRM, err := utils.GetResMapWithIDAnnotation(rm)
	if err != nil {
		return err
	}

	// encode the ResMap so it can be fed to the plugin
	resources, err := inputRM.AsYaml()
	if err != nil {
		return err
	}

	// invoke the plugin with resources as the input
	output, err := p.invokePlugin(resources)
	if err != nil {
		return fmt.Errorf("%v %s", err, string(output))
	}

	// update the original ResMap based on the output
	return utils.UpdateResMapValues(p.pluginName, p.h, output, rm)
}

func injectAnnotation(input *yaml.RNode, k, v string) error {
	err := input.PipeE(yaml.SetAnnotation(k, v))
	if err != nil {
		return err
	}
	return nil
}

// invokePlugin uses Function runner to run function as plugin
func (p *FnPlugin) invokePlugin(input []byte) ([]byte, error) {
	// get function config rnode
	functionConfig, err := bytesToRNode(p.cfg)
	if err != nil {
		return nil, err
	}

	// This annotation will let kustomize ingnore this item in output
	err = injectAnnotation(functionConfig, "config.kubernetes.io/local-config", "true")
	if err != nil {
		return nil, err
	}
	// we need to add config as input for generators. Some of them don't work with FunctionConfig
	// and in addition kio.Pipeline won't create anything if there are no objects
	// see https://github.com/kubernetes-sigs/kustomize/blob/master/kyaml/kio/kio.go#L93
	// Since we added `local-config` annotation so it will be ignored in generator output
	// TODO(donnyxia): This is actually not used by generator and only used to bypass a kio limitation.
	// Need better solution.
	if input == nil {
		yml, err := functionConfig.String()
		if err != nil {
			return nil, err
		}
		input = []byte(yml)
	}

	// Configure and Execute Fn. We don't need to convert resources to ResourceList here
	// because function runtime will do that. See kyaml/fn/runtime/runtimeutil/runtimeutil.go
	var ouputBuffer bytes.Buffer
	p.runFns.Input = bytes.NewReader(input)
	p.runFns.Functions = append(p.runFns.Functions, functionConfig)
	p.runFns.Output = &ouputBuffer

	err = p.runFns.Execute()
	if err != nil {
		return nil, errors.WrapPrefixf(
			err, "couldn't execute function")
	}

	return ouputBuffer.Bytes(), nil
}