Skip to content

Commit f21bd9e

Browse files
authored
Merge pull request kubernetes#89745 from johnbelamaric/kubeconform-cleanup
Various cleanup for the kubeconform command
2 parents 46b2891 + de5d8d6 commit f21bd9e

File tree

4 files changed

+121
-54
lines changed

4 files changed

+121
-54
lines changed
Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
# Kubetestgen
1+
# kubeconform
22

3-
kubetestgen generates a list of behaviors for a resource based on the OpenAPI schema. The purpose is to bootstrap a list of behaviors, and not to produce the final list of behaviors. We expect that the resulting files will be curated to identify a meaningful set of behaviors for the conformance requirements of the targeted resource. This may include addition, modification, and removal of behaviors from the generated list.
3+
`kubeconform` is used to manage the creation and coverage analysis of conformance behaviors and tests. Currently it performs two functions:
44

5+
* `gen`. This command generates a list of behaviors for a resource based on the OpenAPI schema. The purpose is to bootstrap a list of behaviors, and not to produce the final list of behaviors. We expect that the resulting files will be curated to identify a meaningful set of behaviors for the conformance requirements of the targeted resource. This may include addition, modification, and removal of behaviors from the generated list.
6+
* `link`. This command prints the defined behaviors not covered by any test.
7+
8+
## gen
59
**Example usage for PodSpec:**
610

11+
From the root directory of the k/k repo, will produce `pod.yaml` in
12+
`test/conformance/behaviors`. The `pwd` is needed because of how bazel handles
13+
working directories with `run`.
14+
715
```
8-
bazel build //test/conformance/kubetestgen:kubetestgen
9-
/bazel-out/k8-fastbuild/bin/test/conformance/kubetestgen/linux_amd64_stripped/kubetestgen --resource io.k8s.api.core.v1.PodSpec --area pod --schema api/openapi-spec/swagger.json --dir test/conformance/behaviors/
16+
$ bazel run //test/conformance/kubeconform:kubeconform -- --resource io.k8s.api.core.v1.PodSpec --area pod --schema api/openapi-spec/swagger.json --dir `pwd`/test/conformance/behaviors/ gen
1017
```
1118

1219
**Flags:**
@@ -17,3 +24,9 @@ bazel build //test/conformance/kubetestgen:kubetestgen
1724
- `dir` - the path to the behaviors directory (default current directory)
1825

1926
**Note**: The tool automatically generates suites based on the object type for a field. All primitive data types are grouped into a default suite, while object data types are grouped into their own suite, one per object.
27+
28+
## link
29+
30+
```
31+
$ bazel run //test/conformance/kubeconform:kubeconform -- -dir `pwd`/test/conformance/behaviors/sig-node -testdata `pwd`/test/conformance/testdata/conformance.yaml link
32+
```

test/conformance/kubeconform/gen.go

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package main
1919
import (
2020
"fmt"
2121
"os"
22+
"path/filepath"
2223
"sort"
2324
"strings"
2425

@@ -31,12 +32,11 @@ import (
3132

3233
var defMap map[string]analysis.SchemaRef
3334

34-
func gen(o *options) {
35+
func gen(o *options) error {
3536
defMap = make(map[string]analysis.SchemaRef)
3637
d, err := loads.JSONSpec(o.schemaPath)
3738
if err != nil {
38-
fmt.Printf("ERROR: %s\n", err.Error())
39-
os.Exit(1)
39+
return err
4040
}
4141
defs := d.Analyzer.AllDefinitions()
4242
sort.Slice(defs, func(i, j int) bool { return defs[i].Name < defs[j].Name })
@@ -105,27 +105,25 @@ func gen(o *options) {
105105

106106
var area behaviors.Area = behaviors.Area{Area: o.area, Suites: suites}
107107
countFields(suites)
108-
printYAML(o.behaviorsDir+o.area, area)
108+
return printYAML(filepath.Join(o.behaviorsDir, o.area), area)
109109
}
110110

111-
func printYAML(fileName string, areaO behaviors.Area) {
111+
func printYAML(fileName string, areaO behaviors.Area) error {
112112
f, err := os.Create(fileName + ".yaml")
113113
if err != nil {
114-
fmt.Printf("ERROR: %s\n", err.Error())
115-
os.Exit(1)
114+
return err
116115
}
117116
defer f.Close()
118117
y, err := yaml.Marshal(areaO)
119118
if err != nil {
120-
fmt.Printf("ERROR: %s\n", err.Error())
121-
os.Exit(1)
119+
return err
122120
}
123121

124122
_, err = f.WriteString(string(y))
125123
if err != nil {
126-
fmt.Printf("ERROR: %s\n", err.Error())
127-
os.Exit(1)
124+
return err
128125
}
126+
return nil
129127
}
130128

131129
func countFields(suites []behaviors.Suite) {

test/conformance/kubeconform/kubeconform.go

Lines changed: 73 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,46 +19,101 @@ package main
1919
import (
2020
"flag"
2121
"fmt"
22+
"os"
2223
)
2324

25+
// homegrown command structures now but if this grows we may
26+
// want to adopt whatever kubectl uses
2427
type options struct {
2528
// Flags only used for generating behaviors
2629
schemaPath string
2730
resource string
2831
area string
2932

3033
// Flags only used for linking behaviors
31-
testdata string
32-
listMissing bool
34+
testdata string
35+
listAll bool
3336

3437
// Flags shared between CLI tools
3538
behaviorsDir string
3639
}
3740

38-
func parseFlags() *options {
41+
type actionFunc func(*options) error
42+
43+
func parseFlags() (actionFunc, *options) {
3944
o := &options{}
4045

41-
flag.StringVar(&o.schemaPath, "schema", "", "Path to the OpenAPI schema")
42-
flag.StringVar(&o.resource, "resource", ".*", "Resource name")
43-
flag.StringVar(&o.area, "area", "default", "Area name to use")
46+
f := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
47+
f.StringVar(&o.schemaPath, "schema", "", "Path to the OpenAPI schema")
48+
f.StringVar(&o.resource, "resource", "", "Resource name")
49+
f.StringVar(&o.area, "area", "", "Area name to use")
50+
51+
f.StringVar(&o.testdata, "testdata", "test/conformance/testdata/conformance.yaml", "YAML file containing test linkage data")
52+
f.BoolVar(&o.listAll, "all", false, "List all behaviors, not just those missing tests")
4453

45-
flag.StringVar(&o.testdata, "testdata", "../testdata/conformance.yaml", "YAML file containing test linkage data")
46-
flag.BoolVar(&o.listMissing, "missing", true, "Only list behaviors missing tests")
54+
f.StringVar(&o.behaviorsDir, "dir", "test/conformance/behaviors/", "Path to the behaviors directory")
4755

48-
flag.StringVar(&o.behaviorsDir, "dir", "../behaviors", "Path to the behaviors directory")
56+
f.Usage = func() {
57+
fmt.Fprintf(os.Stderr,
58+
"USAGE\n-----\n%s [ options ] { link | gen }\n",
59+
os.Args[0])
60+
fmt.Fprintf(os.Stderr, "\nOPTIONS\n-------\n")
61+
flag.PrintDefaults()
62+
fmt.Fprintf(os.Stderr, "\nACTIONS\n------------")
63+
fmt.Fprintf(os.Stderr, `
64+
'link' lists behaviors associated with tests
65+
'gen' generates behaviors based on the API schema
66+
`)
67+
}
4968

69+
flag.CommandLine = f
5070
flag.Parse()
51-
return o
71+
if len(flag.Args()) != 1 {
72+
flag.CommandLine.Usage()
73+
os.Exit(2)
74+
}
75+
76+
var action actionFunc
77+
switch flag.Args()[0] {
78+
case "gen":
79+
action = gen
80+
if o.schemaPath == "" {
81+
action = nil
82+
fmt.Fprintf(os.Stderr, "-schema is required for 'gen'\n")
83+
}
84+
if o.resource == "" {
85+
action = nil
86+
fmt.Fprintf(os.Stderr, "-resource is required for 'gen'\n")
87+
}
88+
if o.area == "" {
89+
action = nil
90+
fmt.Fprintf(os.Stderr, "-area is required for 'gen'\n")
91+
}
92+
case "link":
93+
action = link
94+
if o.testdata == "" {
95+
action = nil
96+
fmt.Fprintf(os.Stderr, "-testdata is required for 'link'\n")
97+
}
98+
}
99+
100+
if o.behaviorsDir == "" {
101+
action = nil
102+
fmt.Fprintf(os.Stderr, "-dir is required\n")
103+
}
104+
105+
if action == nil {
106+
flag.CommandLine.Usage()
107+
os.Exit(2)
108+
}
109+
return action, o
52110
}
53111

54112
func main() {
55-
o := parseFlags()
56-
action := flag.Arg(0)
57-
if action == "gen" {
58-
gen(o)
59-
} else if action == "link" {
60-
link(o)
61-
} else {
62-
fmt.Printf("Unknown argument %s\n", action)
113+
action, o := parseFlags()
114+
err := action(o)
115+
if err != nil {
116+
fmt.Printf("Error: %s\n", err)
117+
os.Exit(1)
63118
}
64119
}

test/conformance/kubeconform/link.go

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ import (
2828
"k8s.io/kubernetes/test/conformance/behaviors"
2929
)
3030

31-
func link(o *options) {
31+
func link(o *options) error {
3232
var behaviorFiles []string
3333
behaviorsMapping := make(map[string][]string)
3434
var conformanceDataList []behaviors.ConformanceData
3535

3636
err := filepath.Walk(o.behaviorsDir,
3737
func(path string, info os.FileInfo, err error) error {
3838
if err != nil {
39-
fmt.Printf("%v", err)
39+
return err
4040
}
4141
r, _ := regexp.Compile(".+.yaml$")
4242
if r.MatchString(path) {
@@ -45,23 +45,30 @@ func link(o *options) {
4545
return nil
4646
})
4747
if err != nil {
48-
fmt.Printf("%v", err)
49-
return
48+
return err
49+
}
50+
fmt.Println()
51+
fmt.Printf("Using behaviors from these %d files:\n", len(behaviorFiles))
52+
for _, f := range behaviorFiles {
53+
fmt.Println(" ", f)
54+
}
55+
fmt.Println()
56+
if o.listAll {
57+
fmt.Println("All behaviors:")
58+
} else {
59+
fmt.Println("Behaviors not covered by any conformance test:")
5060
}
51-
fmt.Printf("%v", behaviorFiles)
5261

5362
for _, behaviorFile := range behaviorFiles {
5463
var suite behaviors.Suite
5564

5665
yamlFile, err := ioutil.ReadFile(behaviorFile)
5766
if err != nil {
58-
fmt.Printf("%v", err)
59-
return
67+
return err
6068
}
6169
err = yaml.UnmarshalStrict(yamlFile, &suite)
6270
if err != nil {
63-
fmt.Printf("%v", err)
64-
return
71+
return err
6572
}
6673

6774
for _, behavior := range suite.Behaviors {
@@ -71,36 +78,30 @@ func link(o *options) {
7178

7279
conformanceYaml, err := ioutil.ReadFile(o.testdata)
7380
if err != nil {
74-
fmt.Printf("%v", err)
75-
return
81+
return err
7682
}
7783

7884
err = yaml.Unmarshal(conformanceYaml, &conformanceDataList)
7985
if err != nil {
80-
fmt.Printf("%v", err)
81-
return
86+
return err
8287
}
8388

8489
for _, data := range conformanceDataList {
8590
for _, behaviorID := range data.Behaviors {
8691
if _, ok := behaviorsMapping[behaviorID]; !ok {
87-
fmt.Printf("Error, cannot find behavior \"%s\"", behaviorID)
88-
return
92+
return fmt.Errorf("cannot find behavior %q", behaviorID)
8993
}
9094
behaviorsMapping[behaviorID] = append(behaviorsMapping[behaviorID], data.CodeName)
9195
}
9296
}
9397
printBehaviorsMapping(behaviorsMapping, o)
98+
return nil
9499
}
95100

96101
func printBehaviorsMapping(behaviorsMapping map[string][]string, o *options) {
97102
for behaviorID, tests := range behaviorsMapping {
98-
if o.listMissing {
99-
if tests == nil {
100-
fmt.Println(behaviorID)
101-
} else {
102-
fmt.Println(behaviorID)
103-
}
103+
if o.listAll || tests == nil {
104+
fmt.Println(" ", behaviorID)
104105
}
105106
}
106107
}

0 commit comments

Comments
 (0)