Skip to content

Commit 4a800c5

Browse files
authored
test: add applier tool (#11143)
Signed-off-by: Yuval Kohavi <yuval.kohavi@gmail.com>
1 parent b3eec04 commit 4a800c5

File tree

8 files changed

+1402
-0
lines changed

8 files changed

+1402
-0
lines changed

hack/utils/applier/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# How to use this?
2+
3+
Run like so
4+
5+
```
6+
go run main.go apply -f yamls.yaml --iterations 3000
7+
```
8+
This will apply the template in yamls.yaml 3000 times.
9+
The template has an `.Index` variable you can use.
10+
11+
For example, for a pod, you would have:
12+
```yaml
13+
apiVersion: v1
14+
kind: Pod
15+
metadata:
16+
name: app-{{.Index}}
17+
namespace: gloo-system
18+
labels:
19+
app: app-{{.Index}}
20+
test: test1
21+
spec:
22+
containers:
23+
- name: app
24+
image: registry.k8s.io/pause:3.6
25+
resources:
26+
limits:
27+
memory: "1Mi"
28+
cpu: "1m"
29+
ports:
30+
- containerPort: 8080
31+
```
32+
33+
this will create pods name `app-0`, `app-1`,...
34+
35+
You can also:
36+
- use `--dry-run` to just print yamls.
37+
- adjust `--qps` and `--burst` as well.
38+
- If your broke the cluster, you can use `--start` to continue from where you left off.
39+
40+
By default, objects will not be overridden. This is because
41+
that a common failure mode here is that the cluster stops responding. When the cluster recovers you don't need to re-create the same object, so a simple create is enough. To change this behavior use `--force` to first delete objects and then re-create them.
42+
43+
To clean up (just delete the objects), pass the `--delete` flag.
44+
45+
If you are still not hitting the QPS set, try adding the `--async` flag to have requests going in parallel (you can also adjust `--workers`).
46+
47+
## Caveat
48+
49+
The template is parsed on the field level. This means that the input files must be valid yaml files. This is because the yaml is parsed **before** the template is evaluated.
50+
Only after the yaml is parsed, we go over all the fields and evaluate the templates on each field.
51+
52+
This is a technical limitation is due to trying to give a similar experience to `kubectl apply`.

hack/utils/applier/cmd/apply.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
"k8s.io/cli-runtime/pkg/genericclioptions"
8+
"k8s.io/cli-runtime/pkg/resource"
9+
"k8s.io/client-go/dynamic"
10+
"k8s.io/client-go/tools/clientcmd"
11+
cmdutil "k8s.io/kubectl/pkg/cmd/util"
12+
13+
"github.com/kgateway-dev/kgateway/hack/utils/applier/pkg/applier"
14+
)
15+
16+
var (
17+
fileNameFlags *genericclioptions.FileNameFlags
18+
19+
dryRun bool
20+
startIndex int
21+
endIndex int
22+
numIterations int
23+
force bool
24+
25+
delete bool
26+
27+
async bool
28+
workers int
29+
30+
qps float32
31+
burst int
32+
)
33+
34+
// applyCmd represents the apply command
35+
var applyCmd = &cobra.Command{
36+
Use: "apply",
37+
RunE: func(cmd *cobra.Command, args []string) error {
38+
fmt.Println("apply called")
39+
40+
if burst < int(qps) {
41+
burst = int(qps)
42+
}
43+
44+
configFlags.WithDiscoveryBurst(burst).WithDiscoveryQPS(qps)
45+
46+
userSpecifiedContext, err := cmd.Flags().GetString("context")
47+
if err != nil {
48+
return err
49+
}
50+
51+
restConfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
52+
&clientcmd.ClientConfigLoadingRules{ExplicitPath: configFlags.ToRawKubeConfigLoader().ConfigAccess().GetDefaultFilename()},
53+
&clientcmd.ConfigOverrides{
54+
CurrentContext: userSpecifiedContext,
55+
}).ClientConfig()
56+
if err != nil {
57+
return err
58+
}
59+
60+
restConfig.QPS = qps
61+
restConfig.Burst = burst
62+
63+
factory := cmdutil.NewFactory(matchVersionKubeConfigFlags)
64+
validationDirective, err := cmdutil.GetValidationDirective(cmd)
65+
if err != nil {
66+
return err
67+
}
68+
69+
dynamicClient, err := dynamic.NewForConfig(restConfig)
70+
if err != nil {
71+
return err
72+
}
73+
fieldValidationVerifier := resource.NewQueryParamVerifier(dynamicClient, factory.OpenAPIGetter(), resource.QueryParamFieldValidation)
74+
75+
validator, err := factory.Validator(validationDirective, fieldValidationVerifier)
76+
if err != nil {
77+
return err
78+
}
79+
filenameOptions := fileNameFlags.ToOptions()
80+
81+
if numIterations > 0 {
82+
endIndex = startIndex + numIterations
83+
}
84+
85+
a := applier.Applier{
86+
Start: startIndex,
87+
End: endIndex,
88+
DryRun: dryRun,
89+
Force: force,
90+
Delete: delete,
91+
92+
Async: async,
93+
Workers: workers,
94+
}
95+
96+
return a.Apply(dynamicClient, factory, filenameOptions, validator)
97+
},
98+
}
99+
100+
func init() {
101+
rootCmd.AddCommand(applyCmd)
102+
103+
filenames := []string{}
104+
recursive := false
105+
kustomize := ""
106+
fileNameFlags = &genericclioptions.FileNameFlags{Usage: "", Filenames: &filenames, Kustomize: &kustomize, Recursive: &recursive}
107+
108+
fileNameFlags.AddFlags(applyCmd.PersistentFlags())
109+
cmdutil.AddValidateFlags(applyCmd)
110+
111+
applyCmd.Flags().IntVar(&startIndex, "start", 0, "Start index for the loop")
112+
applyCmd.Flags().IntVar(&numIterations, "iterations", 0, "If set, end index will be set to start+this")
113+
applyCmd.Flags().IntVar(&endIndex, "end", 3000, "End index for the loop. (If start is 0, this is the number times to apply the manifest)")
114+
applyCmd.Flags().BoolVarP(&dryRun, "dry-run", "d", false, "Dry run - print yamls to stdout")
115+
applyCmd.Flags().BoolVar(&force, "force", false, "Force apply - delete and recreate objects")
116+
applyCmd.Flags().Float32Var(&qps, "qps", 50, "QPS")
117+
applyCmd.Flags().IntVar(&burst, "burst", 75, "Burst")
118+
119+
applyCmd.Flags().BoolVar(&async, "async", false, "Run in async mode. Use this if not hitting your QPS.")
120+
applyCmd.Flags().IntVar(&workers, "workers", 10, "Number of workers to use when using async mode. each worker submits requests in parallel.")
121+
122+
applyCmd.Flags().BoolVar(&delete, "delete", false, "Delete resources instead of applying them (useful for cleanup)")
123+
}

hack/utils/applier/cmd/root.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package cmd
2+
3+
import (
4+
"os"
5+
6+
"github.com/spf13/cobra"
7+
"k8s.io/cli-runtime/pkg/genericclioptions"
8+
cmdutil "k8s.io/kubectl/pkg/cmd/util"
9+
)
10+
11+
var (
12+
configFlags = genericclioptions.NewConfigFlags(true)
13+
matchVersionKubeConfigFlags = cmdutil.NewMatchVersionFlags(configFlags)
14+
)
15+
16+
// rootCmd represents the base command when called without any subcommands
17+
var rootCmd = &cobra.Command{
18+
Use: "applier",
19+
}
20+
21+
// Execute adds all child commands to the root command and sets flags appropriately.
22+
// This is called by main.main(). It only needs to happen once to the rootCmd.
23+
func Execute() {
24+
err := rootCmd.Execute()
25+
if err != nil {
26+
os.Exit(1)
27+
}
28+
}
29+
30+
func init() {
31+
configFlags.AddFlags(rootCmd.PersistentFlags())
32+
matchVersionKubeConfigFlags.AddFlags(rootCmd.PersistentFlags())
33+
34+
}

hack/utils/applier/go.mod

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
module github.com/kgateway-dev/kgateway/hack/utils/applier
2+
3+
go 1.19
4+
5+
require (
6+
github.com/cheggaaa/pb/v3 v3.1.0
7+
github.com/spf13/cobra v1.5.0
8+
k8s.io/apimachinery v0.25.2
9+
k8s.io/cli-runtime v0.25.2
10+
k8s.io/client-go v0.25.2
11+
k8s.io/kubectl v0.25.2
12+
)
13+
14+
require (
15+
cloud.google.com/go v0.97.0 // indirect
16+
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
17+
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
18+
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
19+
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
20+
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
21+
github.com/Azure/go-autorest/logger v0.2.1 // indirect
22+
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
23+
github.com/MakeNowJust/heredoc v1.0.0 // indirect
24+
github.com/PuerkitoBio/purell v1.1.1 // indirect
25+
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
26+
github.com/VividCortex/ewma v1.1.1 // indirect
27+
github.com/chai2010/gettext-go v1.0.2 // indirect
28+
github.com/davecgh/go-spew v1.1.1 // indirect
29+
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
30+
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
31+
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
32+
github.com/fatih/color v1.10.0 // indirect
33+
github.com/go-errors/errors v1.0.1 // indirect
34+
github.com/go-logr/logr v1.2.3 // indirect
35+
github.com/go-openapi/jsonpointer v0.19.5 // indirect
36+
github.com/go-openapi/jsonreference v0.19.5 // indirect
37+
github.com/go-openapi/swag v0.19.14 // indirect
38+
github.com/gogo/protobuf v1.3.2 // indirect
39+
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
40+
github.com/golang/protobuf v1.5.2 // indirect
41+
github.com/google/btree v1.0.1 // indirect
42+
github.com/google/gnostic v0.5.7-v3refs // indirect
43+
github.com/google/gofuzz v1.1.0 // indirect
44+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
45+
github.com/google/uuid v1.1.2 // indirect
46+
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
47+
github.com/imdario/mergo v0.3.6 // indirect
48+
github.com/inconshreveable/mousetrap v1.0.0 // indirect
49+
github.com/josharian/intern v1.0.0 // indirect
50+
github.com/json-iterator/go v1.1.12 // indirect
51+
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
52+
github.com/mailru/easyjson v0.7.6 // indirect
53+
github.com/mattn/go-colorable v0.1.8 // indirect
54+
github.com/mattn/go-isatty v0.0.12 // indirect
55+
github.com/mattn/go-runewidth v0.0.12 // indirect
56+
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
57+
github.com/moby/spdystream v0.2.0 // indirect
58+
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
59+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
60+
github.com/modern-go/reflect2 v1.0.2 // indirect
61+
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
62+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
63+
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
64+
github.com/pkg/errors v0.9.1 // indirect
65+
github.com/rivo/uniseg v0.2.0 // indirect
66+
github.com/russross/blackfriday v1.5.2 // indirect
67+
github.com/spf13/pflag v1.0.5 // indirect
68+
github.com/xlab/treeprint v1.1.0 // indirect
69+
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
70+
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
71+
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
72+
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
73+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
74+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
75+
golang.org/x/text v0.3.7 // indirect
76+
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
77+
google.golang.org/appengine v1.6.7 // indirect
78+
google.golang.org/protobuf v1.28.0 // indirect
79+
gopkg.in/inf.v0 v0.9.1 // indirect
80+
gopkg.in/yaml.v2 v2.4.0 // indirect
81+
gopkg.in/yaml.v3 v3.0.1 // indirect
82+
k8s.io/api v0.25.2 // indirect
83+
k8s.io/component-base v0.25.2 // indirect
84+
k8s.io/klog/v2 v2.70.1 // indirect
85+
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
86+
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
87+
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
88+
sigs.k8s.io/kustomize/api v0.12.1 // indirect
89+
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
90+
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
91+
sigs.k8s.io/yaml v1.2.0 // indirect
92+
)

0 commit comments

Comments
 (0)