mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 10:20:51 +00:00
Merge pull request #114948 from haoruan/feature-api-doc-field-linter
Add linter to check if api docs match field tag names
This commit is contained in:
commit
5d4776adc1
4
api/openapi-spec/swagger.json
generated
4
api/openapi-spec/swagger.json
generated
@ -14122,12 +14122,12 @@
|
|||||||
"description": "CustomResourceConversion describes how to convert different versions of a CR.",
|
"description": "CustomResourceConversion describes how to convert different versions of a CR.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"description": "strategy specifies how custom resources are converted between versions. Allowed values are: - `None`: The converter only change the apiVersion and would not touch any other field in the custom resource. - `Webhook`: API Server will call to an external webhook to do the conversion. Additional information\n is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set.",
|
"description": "strategy specifies how custom resources are converted between versions. Allowed values are: - `\"None\"`: The converter only change the apiVersion and would not touch any other field in the custom resource. - `\"Webhook\"`: API Server will call to an external webhook to do the conversion. Additional information\n is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"webhook": {
|
"webhook": {
|
||||||
"$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.WebhookConversion",
|
"$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.WebhookConversion",
|
||||||
"description": "webhook describes how to call the conversion webhook. Required when `strategy` is set to `Webhook`."
|
"description": "webhook describes how to call the conversion webhook. Required when `strategy` is set to `\"Webhook\"`."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"default": "",
|
"default": "",
|
||||||
"description": "strategy specifies how custom resources are converted between versions. Allowed values are: - `None`: The converter only change the apiVersion and would not touch any other field in the custom resource. - `Webhook`: API Server will call to an external webhook to do the conversion. Additional information\n is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set.",
|
"description": "strategy specifies how custom resources are converted between versions. Allowed values are: - `\"None\"`: The converter only change the apiVersion and would not touch any other field in the custom resource. - `\"Webhook\"`: API Server will call to an external webhook to do the conversion. Additional information\n is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"webhook": {
|
"webhook": {
|
||||||
@ -54,7 +54,7 @@
|
|||||||
"$ref": "#/components/schemas/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.WebhookConversion"
|
"$ref": "#/components/schemas/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.WebhookConversion"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "webhook describes how to call the conversion webhook. Required when `strategy` is set to `Webhook`."
|
"description": "webhook describes how to call the conversion webhook. Required when `strategy` is set to `\"Webhook\"`."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
105
cmd/fieldnamedocscheck/field_name_docs_check.go
Normal file
105
cmd/fieldnamedocscheck/field_name_docs_check.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
kruntime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
typeSrc = flag.StringP("type-src", "s", "", "From where we are going to read the types")
|
||||||
|
re = regexp.MustCompile("`(\\b\\w+\\b)`")
|
||||||
|
)
|
||||||
|
|
||||||
|
// kubeTypesMap is a map from field name to its tag name and doc.
|
||||||
|
type kubeTypesMap map[string]kruntime.Pair
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *typeSrc == "" {
|
||||||
|
klog.Fatalf("Please define -s flag as it is the api type file")
|
||||||
|
}
|
||||||
|
|
||||||
|
docsForTypes := kruntime.ParseDocumentationFrom(*typeSrc)
|
||||||
|
rc := false
|
||||||
|
|
||||||
|
for _, ks := range docsForTypes {
|
||||||
|
typesMap := make(kubeTypesMap)
|
||||||
|
|
||||||
|
for _, p := range ks[1:] {
|
||||||
|
// skip the field with no tag name
|
||||||
|
if p.Name != "" {
|
||||||
|
typesMap[strings.ToLower(p.Name)] = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
structName := ks[0].Name
|
||||||
|
|
||||||
|
rc = checkFieldNameAndDoc(structName, "", ks[0].Doc, typesMap)
|
||||||
|
for _, p := range ks[1:] {
|
||||||
|
rc = checkFieldNameAndDoc(structName, p.Name, p.Doc, typesMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rc {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkFieldNameAndDoc(structName, fieldName, doc string, typesMap kubeTypesMap) bool {
|
||||||
|
rc := false
|
||||||
|
visited := sets.Set[string]{}
|
||||||
|
|
||||||
|
// The rule is:
|
||||||
|
// 1. Get all back-tick quoted names in the doc
|
||||||
|
// 2. Skip the name which is already found mismatched.
|
||||||
|
// 3. Skip the name whose lowercase is different from the lowercase of tag names,
|
||||||
|
// because some docs use back-tick to quote field value or nil
|
||||||
|
// 4. Check if the name is different from its tag name
|
||||||
|
|
||||||
|
// TODO: a manual pass adding back-ticks to the doc strings, then update the linter to
|
||||||
|
// check the existence of back-ticks
|
||||||
|
nameGroups := re.FindAllStringSubmatch(doc, -1)
|
||||||
|
for _, nameGroup := range nameGroups {
|
||||||
|
name := nameGroup[1]
|
||||||
|
if visited.Has(name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if p, ok := typesMap[strings.ToLower(name)]; ok && p.Name != name {
|
||||||
|
rc = true
|
||||||
|
visited.Insert(name)
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: doc for %s", structName)
|
||||||
|
if fieldName != "" {
|
||||||
|
fmt.Fprintf(os.Stderr, ".%s", fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, " contains: %s, which should be: %s\n", name, p.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc
|
||||||
|
}
|
65
hack/verify-fieldname-docs.sh
Executable file
65
hack/verify-fieldname-docs.sh
Executable file
@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright 2023 The Kubernetes Authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# This script checks API-related files for mismatch in docs and field names,
|
||||||
|
# and outputs a list of fields that their docs and field names are mismatched.
|
||||||
|
# Usage: `hack/verify-fieldname-docs.sh`.
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
|
||||||
|
source "${KUBE_ROOT}/hack/lib/init.sh"
|
||||||
|
source "${KUBE_ROOT}/hack/lib/util.sh"
|
||||||
|
|
||||||
|
kube::golang::setup_env
|
||||||
|
|
||||||
|
make -C "${KUBE_ROOT}" WHAT=cmd/fieldnamedocscheck
|
||||||
|
|
||||||
|
# Find binary
|
||||||
|
fieldnamedocscheck=$(kube::util::find-binary "fieldnamedocscheck")
|
||||||
|
|
||||||
|
result=0
|
||||||
|
|
||||||
|
find_files() {
|
||||||
|
find . -not \( \
|
||||||
|
\( \
|
||||||
|
-wholename './output' \
|
||||||
|
-o -wholename './_output' \
|
||||||
|
-o -wholename './_gopath' \
|
||||||
|
-o -wholename './release' \
|
||||||
|
-o -wholename './target' \
|
||||||
|
-o -wholename '*/third_party/*' \
|
||||||
|
-o -wholename '*/vendor/*' \
|
||||||
|
-o -wholename './pkg/*' \
|
||||||
|
\) -prune \
|
||||||
|
\) \
|
||||||
|
\( -wholename './staging/src/k8s.io/api/*/v*/types.go' \
|
||||||
|
-o -wholename './staging/src/k8s.io/kube-aggregator/pkg/apis/*/v*/types.go' \
|
||||||
|
-o -wholename './staging/src/k8s.io/apiextensions-apiserver/pkg/apis/*/v*/types.go' \
|
||||||
|
\)
|
||||||
|
}
|
||||||
|
|
||||||
|
versioned_api_files=$(find_files) || true
|
||||||
|
|
||||||
|
for file in ${versioned_api_files}; do
|
||||||
|
package="${file%"/types.go"}"
|
||||||
|
echo "Checking ${package}"
|
||||||
|
${fieldnamedocscheck} -s "${file}" || result=$?
|
||||||
|
done
|
||||||
|
|
||||||
|
exit ${result}
|
4
pkg/generated/openapi/zz_generated.openapi.go
generated
4
pkg/generated/openapi/zz_generated.openapi.go
generated
@ -44441,7 +44441,7 @@ func schema_pkg_apis_apiextensions_v1_CustomResourceConversion(ref common.Refere
|
|||||||
Properties: map[string]spec.Schema{
|
Properties: map[string]spec.Schema{
|
||||||
"strategy": {
|
"strategy": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Description: "strategy specifies how custom resources are converted between versions. Allowed values are: - `None`: The converter only change the apiVersion and would not touch any other field in the custom resource. - `Webhook`: API Server will call to an external webhook to do the conversion. Additional information\n is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set.",
|
Description: "strategy specifies how custom resources are converted between versions. Allowed values are: - `\"None\"`: The converter only change the apiVersion and would not touch any other field in the custom resource. - `\"Webhook\"`: API Server will call to an external webhook to do the conversion. Additional information\n is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set.",
|
||||||
Default: "",
|
Default: "",
|
||||||
Type: []string{"string"},
|
Type: []string{"string"},
|
||||||
Format: "",
|
Format: "",
|
||||||
@ -44449,7 +44449,7 @@ func schema_pkg_apis_apiextensions_v1_CustomResourceConversion(ref common.Refere
|
|||||||
},
|
},
|
||||||
"webhook": {
|
"webhook": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Description: "webhook describes how to call the conversion webhook. Required when `strategy` is set to `Webhook`.",
|
Description: "webhook describes how to call the conversion webhook. Required when `strategy` is set to `\"Webhook\"`.",
|
||||||
Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.WebhookConversion"),
|
Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.WebhookConversion"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -107,12 +107,12 @@ message CustomResourceColumnDefinition {
|
|||||||
// CustomResourceConversion describes how to convert different versions of a CR.
|
// CustomResourceConversion describes how to convert different versions of a CR.
|
||||||
message CustomResourceConversion {
|
message CustomResourceConversion {
|
||||||
// strategy specifies how custom resources are converted between versions. Allowed values are:
|
// strategy specifies how custom resources are converted between versions. Allowed values are:
|
||||||
// - `None`: The converter only change the apiVersion and would not touch any other field in the custom resource.
|
// - `"None"`: The converter only change the apiVersion and would not touch any other field in the custom resource.
|
||||||
// - `Webhook`: API Server will call to an external webhook to do the conversion. Additional information
|
// - `"Webhook"`: API Server will call to an external webhook to do the conversion. Additional information
|
||||||
// is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set.
|
// is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set.
|
||||||
optional string strategy = 1;
|
optional string strategy = 1;
|
||||||
|
|
||||||
// webhook describes how to call the conversion webhook. Required when `strategy` is set to `Webhook`.
|
// webhook describes how to call the conversion webhook. Required when `strategy` is set to `"Webhook"`.
|
||||||
// +optional
|
// +optional
|
||||||
optional WebhookConversion webhook = 2;
|
optional WebhookConversion webhook = 2;
|
||||||
}
|
}
|
||||||
|
@ -74,12 +74,12 @@ type CustomResourceDefinitionSpec struct {
|
|||||||
// CustomResourceConversion describes how to convert different versions of a CR.
|
// CustomResourceConversion describes how to convert different versions of a CR.
|
||||||
type CustomResourceConversion struct {
|
type CustomResourceConversion struct {
|
||||||
// strategy specifies how custom resources are converted between versions. Allowed values are:
|
// strategy specifies how custom resources are converted between versions. Allowed values are:
|
||||||
// - `None`: The converter only change the apiVersion and would not touch any other field in the custom resource.
|
// - `"None"`: The converter only change the apiVersion and would not touch any other field in the custom resource.
|
||||||
// - `Webhook`: API Server will call to an external webhook to do the conversion. Additional information
|
// - `"Webhook"`: API Server will call to an external webhook to do the conversion. Additional information
|
||||||
// is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set.
|
// is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set.
|
||||||
Strategy ConversionStrategyType `json:"strategy" protobuf:"bytes,1,name=strategy"`
|
Strategy ConversionStrategyType `json:"strategy" protobuf:"bytes,1,name=strategy"`
|
||||||
|
|
||||||
// webhook describes how to call the conversion webhook. Required when `strategy` is set to `Webhook`.
|
// webhook describes how to call the conversion webhook. Required when `strategy` is set to `"Webhook"`.
|
||||||
// +optional
|
// +optional
|
||||||
Webhook *WebhookConversion `json:"webhook,omitempty" protobuf:"bytes,2,opt,name=webhook"`
|
Webhook *WebhookConversion `json:"webhook,omitempty" protobuf:"bytes,2,opt,name=webhook"`
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user