Skip to content

Commit 123f1ec

Browse files
committed
CAD pkg authoring
1 parent ef1a189 commit 123f1ec

File tree

5 files changed

+320
-4
lines changed

5 files changed

+320
-4
lines changed

commands/commands.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ func GetKptCommands(ctx context.Context, name, version string) []*cobra.Command
3535
fnCmd := GetFnCommand(ctx, name)
3636
pkgCmd := GetPkgCommand(ctx, name)
3737
liveCmd := GetLiveCommand(ctx, name, version)
38-
39-
c = append(c, pkgCmd, fnCmd, liveCmd)
38+
editorCmd := EditorCommand(ctx, name)
39+
c = append(c, pkgCmd, fnCmd, liveCmd, editorCmd)
4040

4141
// apply cross-cutting issues to commands
4242
NormalizeCommand(c...)

commands/editor.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package commands
2+
3+
import (
4+
"context"
5+
6+
"github.com/GoogleContainerTools/kpt/internal/cad"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
func EditorCommand(ctx context.Context, name string) *cobra.Command {
11+
/*
12+
editor := &cobra.Command{
13+
Use: "editor",
14+
Short: `Edit local package resources`,
15+
// Aliases: []string{"set", "add"},
16+
RunE: func(cmd *cobra.Command, args []string) error {
17+
return nil
18+
},
19+
}
20+
editor.AddCommand()
21+
return editor
22+
*/
23+
return cad.NewSetter(ctx).Command
24+
}

internal/cad/cmdset.go

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package cad
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"os"
10+
"os/exec"
11+
"path/filepath"
12+
"strings"
13+
14+
"github.com/GoogleContainerTools/kpt/internal/fnruntime"
15+
"github.com/GoogleContainerTools/kpt/internal/gitutil"
16+
"github.com/GoogleContainerTools/kpt/internal/pkg"
17+
"github.com/GoogleContainerTools/kpt/internal/printer"
18+
"github.com/GoogleContainerTools/kpt/internal/types"
19+
"github.com/GoogleContainerTools/kpt/internal/util/argutil"
20+
"github.com/GoogleContainerTools/kpt/internal/util/cmdutil"
21+
fnresult "github.com/GoogleContainerTools/kpt/pkg/api/fnresult/v1"
22+
kptfile "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
23+
"github.com/GoogleContainerTools/kpt/thirdparty/cmdconfig/commands/runner"
24+
"github.com/GoogleContainerTools/kpt/thirdparty/kyaml/runfn"
25+
"github.com/google/shlex"
26+
"github.com/spf13/cobra"
27+
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
28+
"sigs.k8s.io/kustomize/kyaml/kio"
29+
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
30+
)
31+
32+
const gcloudName = "./gcloud-config.yaml"
33+
34+
// This will be replaced by variant constructor
35+
func (r *Setter) GetGcloudFnConfigPath() string {
36+
return filepath.Join(r.Dest, gcloudName)
37+
}
38+
39+
// THis will be supported by variant constructor
40+
var IncludeMetaResourcesFlag = true
41+
42+
func NewSetter(ctx context.Context) *Setter {
43+
r := &Setter{ctx: ctx}
44+
c := &cobra.Command{
45+
Use: "set [--kind=namespace] [--pkg=redis-bucket]",
46+
Short: `make the KRM resource(s) available in the local package`,
47+
Example: `
48+
# Set the package resources to the same namespace
49+
$ editor set -k=namespace
50+
`,
51+
PreRunE: r.preRunE,
52+
RunE: r.runE,
53+
}
54+
55+
c.Flags().StringVarP(&r.kind, "kind", "k", "", "KRM resource `Kind`")
56+
c.RegisterFlagCompletionFunc("kind", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
57+
return kubectlKinds, cobra.ShellCompDirectiveDefault
58+
})
59+
60+
c.Flags().StringVarP(&r.pkg, "pkg", "p", "",
61+
"the package name of KRM resources")
62+
r.Command = c
63+
return r
64+
}
65+
66+
type Setter struct {
67+
kind string
68+
pkg string
69+
70+
// The kpt package directory
71+
Dest string
72+
kf *kptfile.KptFile
73+
Command *cobra.Command
74+
ctx context.Context
75+
fnResults *fnresult.ResultList
76+
}
77+
78+
func (r *Setter) preRunE(c *cobra.Command, args []string) error {
79+
if r.kind == "" && r.pkg == "" {
80+
return fmt.Errorf("must specify a flag `kind` or a `pkg`")
81+
}
82+
if len(args) == 0 {
83+
// no pkg path specified, default to current working dir
84+
wd, err := os.Getwd()
85+
if err != nil {
86+
return err
87+
}
88+
r.Dest = wd
89+
} else {
90+
// resolve and validate the provided path
91+
r.Dest = args[0]
92+
}
93+
var err error
94+
r.Dest, err = argutil.ResolveSymlink(r.ctx, r.Dest)
95+
if err != nil {
96+
return err
97+
}
98+
r.kf, err = pkg.ReadKptfile(r.Dest)
99+
if err != nil {
100+
return err
101+
}
102+
return nil
103+
}
104+
105+
func (r *Setter) fromKubeclCreate() (string, error) {
106+
name := r.GetDefaultName()
107+
if name == "" {
108+
return "", nil
109+
}
110+
var out, errout bytes.Buffer
111+
112+
cmd := exec.Command("kubectl", "create", r.kind, name, "--dry-run=client", "-oyaml")
113+
cmd.Stdout = &out
114+
cmd.Stderr = &errout
115+
err := cmd.Run()
116+
if err != nil {
117+
return "", err
118+
}
119+
if errout.String() != "" {
120+
return "", fmt.Errorf(errout.String())
121+
}
122+
return out.String(), nil
123+
}
124+
125+
func (r *Setter) getFunctionSpec(execPath string) (*runtimeutil.FunctionSpec, []string, error) {
126+
fn := &runtimeutil.FunctionSpec{}
127+
var execArgs []string
128+
s, err := shlex.Split(execPath)
129+
if err != nil {
130+
return nil, nil, fmt.Errorf("exec command %q must be valid: %w", execPath, err)
131+
}
132+
if len(s) > 0 {
133+
fn.Exec.Path = s[0]
134+
execArgs = s[1:]
135+
}
136+
return fn, execArgs, nil
137+
}
138+
139+
// Should be changed to variant constructor.
140+
func (r *Setter) GetDefaultName() string {
141+
kindToName := map[string]string{
142+
"namespace": r.kf.Name,
143+
}
144+
if r.kind != "" {
145+
if name, ok := kindToName[r.kind]; ok {
146+
return name
147+
}
148+
}
149+
return ""
150+
}
151+
152+
// GetExeFnsPath choose which kpt fn executable(s) to run for the given `Kind`.
153+
// The mapping between fn and resource `kind` will be done by function/pkg discovery mechanism.
154+
// TODO: run multiple fn execs in series (like render pipeline)
155+
func GetExeFnsPath(kind string) string {
156+
dir := os.Getenv(gitutil.RepoCacheDirEnv)
157+
if dir != "" {
158+
return dir
159+
}
160+
// cache location unspecified, use UserHomeDir/.kpt/repos
161+
dir, _ = os.UserHomeDir()
162+
execName, ok := BuiltinTransformers[kind]
163+
if !ok {
164+
return ""
165+
}
166+
execPath := filepath.Join(dir, ".kpt", "fns", execName)
167+
if _, err := os.Stat(execPath); errors.Is(err, os.ErrNotExist) {
168+
return ""
169+
}
170+
return execPath
171+
}
172+
173+
func (r *Setter) runE(c *cobra.Command, _ []string) error {
174+
var inputs []kio.Reader
175+
// Leverage kubectl to create k8s Resource (caveat, name is required --> r.kf.name )
176+
if r.kind != "" {
177+
krmResources, err := r.fromKubeclCreate()
178+
if err != nil {
179+
return err
180+
}
181+
if krmResources != "" {
182+
reader := strings.NewReader(krmResources)
183+
inputs = append(inputs, &kio.ByteReader{Reader: reader})
184+
}
185+
}
186+
// find the fn exec that should mutate/validate this `kind` resource.
187+
execPath := GetExeFnsPath(r.kind)
188+
if execPath == "" {
189+
// TODO: write resource to pkg dir.
190+
return nil
191+
}
192+
fnSpec, execArgs, err := r.getFunctionSpec(execPath)
193+
if err != nil {
194+
return err
195+
}
196+
197+
matchFilesGlob := kio.MatchAll
198+
if IncludeMetaResourcesFlag {
199+
matchFilesGlob = append(matchFilesGlob, kptfile.KptFileName)
200+
}
201+
resolvedPath, err := argutil.ResolveSymlink(r.ctx, r.Dest)
202+
if err != nil {
203+
return err
204+
}
205+
functionConfigFilter, err := pkg.FunctionConfigFilterFunc(types.UniquePath(resolvedPath), IncludeMetaResourcesFlag)
206+
if err != nil {
207+
return err
208+
}
209+
210+
inputs = append(inputs, kio.LocalPackageReader{
211+
PackagePath: resolvedPath,
212+
MatchFilesGlob: matchFilesGlob,
213+
FileSkipFunc: functionConfigFilter,
214+
PreserveSeqIndent: true,
215+
PackageFileName: kptfile.KptFileName,
216+
IncludeSubpackages: true,
217+
WrapBareSeqNode: true,
218+
})
219+
var outputs []kio.Writer
220+
configs, err := kio.LocalPackageReader{PackagePath: r.GetGcloudFnConfigPath(), PreserveSeqIndent: true, WrapBareSeqNode: true}.Read()
221+
if err != nil {
222+
return err
223+
}
224+
if len(configs) != 1 {
225+
return fmt.Errorf("expected exactly 1 functionConfig, found %d", len(configs))
226+
}
227+
functionConfig := configs[0]
228+
229+
outputs = append(outputs, kio.ByteWriter{
230+
Writer: printer.FromContextOrDie(r.ctx).OutStream(),
231+
FunctionConfig: functionConfig,
232+
ClearAnnotations: []string{kioutil.IndexAnnotation, kioutil.PathAnnotation}, // nolint:staticcheck
233+
})
234+
var output io.Writer
235+
OutContent := bytes.Buffer{}
236+
output = &OutContent
237+
238+
runFns := runfn.RunFns{
239+
Ctx: r.ctx,
240+
Function: fnSpec,
241+
ExecArgs: execArgs,
242+
OriginalExec: execPath,
243+
Output: output,
244+
Input: nil,
245+
KIOReaders: inputs,
246+
Path: r.Dest,
247+
Network: false,
248+
StorageMounts: nil,
249+
ResultsDir: "",
250+
Env: nil,
251+
AsCurrentUser: false,
252+
FnConfig: nil,
253+
FnConfigPath: r.GetGcloudFnConfigPath(),
254+
IncludeMetaResources: IncludeMetaResourcesFlag,
255+
ImagePullPolicy: fnruntime.IfNotPresentPull,
256+
ContinueOnEmptyResult: true,
257+
Selector: kptfile.Selector{},
258+
}
259+
260+
err = runner.HandleError(r.ctx, runFns.Execute())
261+
if err != nil {
262+
return err
263+
}
264+
return cmdutil.WriteFnOutput(r.Dest, OutContent.String(), false, printer.FromContextOrDie(r.ctx).OutStream())
265+
}

internal/cad/consts.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package cad
2+
3+
var kubectlKinds = []string{
4+
"clusterrole",
5+
"clusterrolebinding",
6+
"configmap",
7+
"cronjob",
8+
"deployment",
9+
"ingress",
10+
"job",
11+
"namespace",
12+
"poddisruptionbudget",
13+
"priorityclass",
14+
"quota",
15+
"role",
16+
"rolebinding",
17+
"secret",
18+
"service",
19+
"serviceaccount",
20+
}
21+
22+
var BuiltinTransformers = map[string]string{
23+
"namespace": "set-namespace",
24+
}

thirdparty/kyaml/runfn/runfn.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ type RunFns struct {
5050
FnConfig *yaml.RNode
5151

5252
// Input can be set to read the Resources from Input rather than from a directory
53-
Input io.Reader
53+
Input io.Reader
54+
KIOReaders []kio.Reader
5455

5556
// Network enables network access for functions that declare it
5657
Network bool
@@ -140,7 +141,9 @@ func (r RunFns) getNodesAndFilters() (
140141
}
141142
}
142143

143-
if r.Input == nil {
144+
if r.KIOReaders != nil {
145+
p.Inputs = r.KIOReaders
146+
} else if r.Input == nil {
144147
p.Inputs = []kio.Reader{outputPkg}
145148
} else {
146149
p.Inputs = []kio.Reader{&kio.ByteReader{Reader: r.Input, PreserveSeqIndent: true, WrapBareSeqNode: true}}

0 commit comments

Comments
 (0)