feat: kubesphere 4.0 (#6115)

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

---------

Signed-off-by: ci-bot <ci-bot@kubesphere.io>
Co-authored-by: ks-ci-bot <ks-ci-bot@example.com>
Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
KubeSphere CI Bot
2024-09-06 11:05:52 +08:00
committed by GitHub
parent b5015ec7b9
commit 447a51f08b
8557 changed files with 546695 additions and 1146174 deletions

View File

@@ -21,6 +21,7 @@ import (
"go/ast"
"go/types"
"sort"
"strings"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -78,6 +79,12 @@ type Generator struct {
// GenerateEmbeddedObjectMeta specifies if any embedded ObjectMeta in the CRD should be generated
GenerateEmbeddedObjectMeta *bool `marker:",optional"`
// HeaderFile specifies the header text (e.g. license) to prepend to generated files.
HeaderFile string `marker:",optional"`
// Year specifies the year to substitute for " YEAR" in the header file.
Year string `marker:",optional"`
}
func (Generator) CheckFilter() loader.NodeFilter {
@@ -128,6 +135,17 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
crdVersions = []string{defaultVersion}
}
var headerText string
if g.HeaderFile != "" {
headerBytes, err := ctx.ReadFile(g.HeaderFile)
if err != nil {
return err
}
headerText = string(headerBytes)
}
headerText = strings.ReplaceAll(headerText, " YEAR", " "+g.Year)
for _, groupKind := range kubeKinds {
parser.NeedCRDFor(groupKind, g.MaxDescLen)
crdRaw := parser.CustomResourceDefinitions[groupKind]
@@ -153,7 +171,7 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
} else {
fileName = fmt.Sprintf("%s_%s.%s.yaml", crdRaw.Spec.Group, crdRaw.Spec.Names.Plural, crdVersions[i])
}
if err := ctx.WriteYAML(fileName, []interface{}{crd}, genall.WithTransform(transformRemoveCRDStatus)); err != nil {
if err := ctx.WriteYAML(fileName, headerText, []interface{}{crd}, genall.WithTransform(transformRemoveCRDStatus), genall.WithTransform(genall.TransformRemoveCreationTimestamp)); err != nil {
return err
}
}

View File

@@ -283,7 +283,7 @@ type Resource struct {
Scope string `marker:",optional"`
}
func (s Resource) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error {
func (s Resource) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, _ string) error {
if s.Path != "" {
crd.Names.Plural = s.Path
}
@@ -362,13 +362,16 @@ type Metadata struct {
Labels []string `marker:",optional"`
}
func (s Metadata) ApplyToCRD(crd *apiext.CustomResourceDefinition, version string) error {
func (s Metadata) ApplyToCRD(crd *apiext.CustomResourceDefinition, _ string) error {
if len(s.Annotations) > 0 {
if crd.Annotations == nil {
crd.Annotations = map[string]string{}
}
for _, str := range s.Annotations {
kv := strings.SplitN(str, "=", 2)
if len(kv) < 2 {
return fmt.Errorf("annotation %s is not in 'xxx=xxx' format", str)
}
crd.Annotations[kv[0]] = kv[1]
}
}

View File

@@ -86,6 +86,9 @@ var FieldOnlyMarkers = []*definitionWithHelp{
must(markers.MakeAnyTypeDefinition("kubebuilder:default", markers.DescribesField, Default{})).
WithHelp(Default{}.Help()),
must(markers.MakeAnyTypeDefinition("kubebuilder:example", markers.DescribesField, Example{})).
WithHelp(Example{}.Help()),
must(markers.MakeDefinition("kubebuilder:validation:EmbeddedResource", markers.DescribesField, XEmbeddedResource{})).
WithHelp(XEmbeddedResource{}.Help()),
@@ -170,7 +173,7 @@ type Pattern string
type MaxItems int
// +controllertools:marker:generateHelp:category="CRD validation"
// MinItems specifies the minimun length for this list.
// MinItems specifies the minimum length for this list.
type MinItems int
// +controllertools:marker:generateHelp:category="CRD validation"
@@ -222,6 +225,19 @@ type Default struct {
Value interface{}
}
// +controllertools:marker:generateHelp:category="CRD validation"
// Example sets the example value for this field.
//
// An example value will be accepted as any value valid for the
// field. Formatting for common types include: boolean: `true`, string:
// `Cluster`, numerical: `1.24`, array: `{1,2}`, object: `{policy:
// "delete"}`). Examples should be defined in pruned form, and only best-effort
// validation will be performed. Full validation of an example requires
// submission of the containing CRD to an apiserver.
type Example struct {
Value interface{}
}
// +controllertools:marker:generateHelp:category="CRD processing"
// PreserveUnknownFields stops the apiserver from pruning fields which are not specified.
//
@@ -461,10 +477,22 @@ func (m Default) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if err != nil {
return err
}
if schema.Type == "array" && string(marshalledDefault) == "{}" {
marshalledDefault = []byte("[]")
}
schema.Default = &apiext.JSON{Raw: marshalledDefault}
return nil
}
func (m Example) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
marshalledExample, err := json.Marshal(m.Value)
if err != nil {
return err
}
schema.Example = &apiext.JSON{Raw: marshalledExample}
return nil
}
func (m XPreserveUnknownFields) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
defTrue := true
schema.XPreserveUnknownFields = &defTrue

View File

@@ -1,5 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright2019 The Kubernetes Authors.
@@ -68,6 +67,22 @@ func (Enum) Help() *markers.DefinitionHelp {
}
}
func (Example) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD validation",
DetailedHelp: markers.DetailedHelp{
Summary: "sets the example value for this field. ",
Details: "An example value will be accepted as any value valid for the field. Formatting for common types include: boolean: `true`, string: `Cluster`, numerical: `1.24`, array: `{1,2}`, object: `{policy: \"delete\"}`). Examples should be defined in pruned form, and only best-effort validation will be performed. Full validation of an example requires submission of the containing CRD to an apiserver.",
},
FieldHelp: map[string]markers.DetailedHelp{
"Value": {
Summary: "",
Details: "",
},
},
}
}
func (ExclusiveMaximum) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD validation",
@@ -202,7 +217,7 @@ func (MinItems) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD validation",
DetailedHelp: markers.DetailedHelp{
Summary: "specifies the minimun length for this list.",
Summary: "specifies the minimum length for this list.",
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{},

View File

@@ -1,5 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright2019 The Kubernetes Authors.
@@ -53,6 +52,14 @@ func (Generator) Help() *markers.DefinitionHelp {
Summary: "specifies if any embedded ObjectMeta in the CRD should be generated",
Details: "",
},
"HeaderFile": {
Summary: "specifies the header text (e.g. license) to prepend to generated files.",
Details: "",
},
"Year": {
Summary: "specifies the year to substitute for \" YEAR\" in the header file.",
Details: "",
},
},
}
}

View File

@@ -176,7 +176,6 @@ type ObjectGenCtx struct {
func writeHeader(pkg *loader.Package, out io.Writer, packageName string, imports *importsList, headerText string) {
// NB(directxman12): blank line after build tags to distinguish them from comments
_, err := fmt.Fprintf(out, `//go:build !ignore_autogenerated
// +build !ignore_autogenerated
%[3]s

View File

@@ -374,7 +374,8 @@ func (c *copyMethodMaker) genMapDeepCopy(actualName *namingInfo, mapType *types.
c.IfElse("val == nil", func() {
c.Line("(*out)[key] = nil")
}, func() {
c.Line("in, out := &val, &outVal")
c.Line("inVal := (*in)[key]")
c.Line("in, out := &inVal, &outVal")
c.genDeepCopyIntoBlock(&namingInfo{typeInfo: mapType.Elem()}, mapType.Elem())
})
c.Line("(*out)[key] = outVal")
@@ -735,11 +736,14 @@ func hasAnyDeepCopyMethod(pkg *loader.Package, typeInfo types.Type) bool {
// eventualUnderlyingType gets the "final" type in a sequence of named aliases.
// It's effectively a shortcut for calling Underlying in a loop.
func eventualUnderlyingType(typeInfo types.Type) types.Type {
last := typeInfo
for underlying := typeInfo.Underlying(); underlying != last; last, underlying = underlying, underlying.Underlying() {
// get the actual underlying type
for {
underlying := typeInfo.Underlying()
if underlying == typeInfo {
break
}
typeInfo = underlying
}
return last
return typeInfo
}
// fineToShallowCopy checks if a shallow-copying a type is equivalent to deepcopy-ing it.

View File

@@ -1,5 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright2019 The Kubernetes Authors.

View File

@@ -20,7 +20,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"golang.org/x/tools/go/packages"
@@ -133,16 +132,28 @@ func WithTransform(transform func(obj map[string]interface{}) error) *WriteYAMLO
}
}
// TransformRemoveCreationTimestamp ensures we do not write the metadata.creationTimestamp field.
func TransformRemoveCreationTimestamp(obj map[string]interface{}) error {
metadata := obj["metadata"].(map[interface{}]interface{})
delete(metadata, "creationTimestamp")
return nil
}
// WriteYAML writes the given objects out, serialized as YAML, using the
// context's OutputRule. Objects are written as separate documents, separated
// from each other by `---` (as per the YAML spec).
func (g GenerationContext) WriteYAML(itemPath string, objs []interface{}, options ...*WriteYAMLOptions) error {
func (g GenerationContext) WriteYAML(itemPath, headerText string, objs []interface{}, options ...*WriteYAMLOptions) error {
out, err := g.Open(nil, itemPath)
if err != nil {
return err
}
defer out.Close()
_, err = out.Write([]byte(headerText))
if err != nil {
return err
}
for _, obj := range objs {
yamlContent, err := yamlMarshal(obj, options...)
if err != nil {
@@ -202,7 +213,7 @@ func (g GenerationContext) ReadFile(path string) ([]byte, error) {
return nil, err
}
defer file.Close()
return ioutil.ReadAll(file)
return io.ReadAll(file)
}
// ForRoots produces a Runtime to run the given generators against the

View File

@@ -136,6 +136,9 @@ func protoFromOptions(optionsRegistry *markers.Registry, options []string) (prot
switch val := val.(type) {
case Generator:
gens = append(gens, &val)
if _, alreadyExists := gensByName[defn.Name]; alreadyExists {
return protoRuntime{}, fmt.Errorf("multiple instances of '%s' generator specified", defn.Name)
}
gensByName[defn.Name] = &val
case OutputRule:
_, genName := splitOutputRuleOption(defn.Name)

View File

@@ -19,7 +19,6 @@ package genall
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
@@ -92,7 +91,7 @@ var OutputToNothing = outputToNothing{}
type outputToNothing struct{}
func (o outputToNothing) Open(_ *loader.Package, _ string) (io.WriteCloser, error) {
return nopCloser{ioutil.Discard}, nil
return nopCloser{io.Discard}, nil
}
// +controllertools:marker:generateHelp:category=""
@@ -122,7 +121,7 @@ var OutputToStdout = outputToStdout{}
// Generally useful for single-artifact outputs.
type outputToStdout struct{}
func (o outputToStdout) Open(_ *loader.Package, itemPath string) (io.WriteCloser, error) {
func (o outputToStdout) Open(_ *loader.Package, _ string) (io.WriteCloser, error) {
return nopCloser{os.Stdout}, nil
}

View File

@@ -1,5 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright2019 The Kubernetes Authors.

View File

@@ -23,7 +23,6 @@ import (
"go/scanner"
"go/token"
"go/types"
"io/ioutil"
"os"
"path/filepath"
"regexp"
@@ -111,7 +110,7 @@ func (p *Package) NeedSyntax() {
for i, filename := range p.CompiledGoFiles {
go func(i int, filename string) {
defer wg.Done()
src, err := ioutil.ReadFile(filename)
src, err := os.ReadFile(filename)
if err != nil {
p.AddError(err)
return

View File

@@ -31,7 +31,7 @@ import (
type Collector struct {
*Registry
byPackage map[string]map[ast.Node]MarkerValues
byPackage map[*loader.Package]map[ast.Node]MarkerValues
mu sync.Mutex
}
@@ -53,7 +53,7 @@ func (c *Collector) init() {
c.Registry = &Registry{}
}
if c.byPackage == nil {
c.byPackage = make(map[string]map[ast.Node]MarkerValues)
c.byPackage = make(map[*loader.Package]map[ast.Node]MarkerValues)
}
}
@@ -75,7 +75,7 @@ func (c *Collector) init() {
func (c *Collector) MarkersInPackage(pkg *loader.Package) (map[ast.Node]MarkerValues, error) {
c.mu.Lock()
c.init()
if markers, exist := c.byPackage[pkg.ID]; exist {
if markers, exist := c.byPackage[pkg]; exist {
c.mu.Unlock()
return markers, nil
}
@@ -91,8 +91,7 @@ func (c *Collector) MarkersInPackage(pkg *loader.Package) (map[ast.Node]MarkerVa
c.mu.Lock()
defer c.mu.Unlock()
c.byPackage[pkg.ID] = markers
c.byPackage[pkg] = markers
return markers, nil
}

View File

@@ -268,7 +268,11 @@ func guessType(scanner *sc.Scanner, raw string, allowSlice bool) *Argument {
subScanner := parserScanner(subRaw, scanner.Error)
var tok rune
for tok = subScanner.Scan(); tok != ',' && tok != sc.EOF && tok != ';'; tok = subScanner.Scan() {
for {
tok = subScanner.Scan()
if tok == ',' || tok == sc.EOF || tok == ';' {
break
}
// wait till we get something interesting
}
@@ -306,6 +310,7 @@ func guessType(scanner *sc.Scanner, raw string, allowSlice bool) *Argument {
// We'll cross that bridge when we get there.
// look ahead till we can figure out if this is a map or a slice
hint = peekNoSpace(subScanner)
firstElemType := guessType(subScanner, subRaw, false)
if firstElemType.Type == StringType {
// might be a map or slice, parse the string and check for colon
@@ -313,8 +318,9 @@ func guessType(scanner *sc.Scanner, raw string, allowSlice bool) *Argument {
var keyVal string // just ignore this
(&Argument{Type: StringType}).parseString(subScanner, raw, reflect.Indirect(reflect.ValueOf(&keyVal)))
if subScanner.Scan() == ':' {
if token := subScanner.Scan(); token == ':' || hint == '}' {
// it's got a string followed by a colon -- it's a map
// or an empty map in case of {}
return &Argument{
Type: MapType,
ItemType: &Argument{Type: AnyType},
@@ -369,6 +375,14 @@ func guessType(scanner *sc.Scanner, raw string, allowSlice bool) *Argument {
// parseString parses either of the two accepted string forms (quoted, or bare tokens).
func (a *Argument) parseString(scanner *sc.Scanner, raw string, out reflect.Value) {
// we need to temporarily disable the scanner's int/float parsing, since we want to
// prevent number parsing errors.
oldMode := scanner.Mode
scanner.Mode = oldMode &^ sc.ScanInts &^ sc.ScanFloats
defer func() {
scanner.Mode = oldMode
}()
// strings are a bit weird -- the "easy" case is quoted strings (tokenized as strings),
// the "hard" case (present for backwards compat) is a bare sequence of tokens that aren't
// a comma.
@@ -495,7 +509,12 @@ func (a *Argument) parse(scanner *sc.Scanner, raw string, out reflect.Value, inS
// raw consumes everything else
castAndSet(out, reflect.ValueOf(raw[scanner.Pos().Offset:]))
// consume everything else
for tok := scanner.Scan(); tok != sc.EOF; tok = scanner.Scan() {
var tok rune
for {
tok = scanner.Scan()
if tok == sc.EOF {
break
}
}
case NumberType:
nextChar := scanner.Peek()

View File

@@ -58,6 +58,13 @@ func extractDoc(node ast.Node, decl *ast.GenDecl) string {
}
outGroup.List = append(outGroup.List, comment)
}
isAsteriskComment := false
for _, l := range outGroup.List {
if strings.HasPrefix(l.Text, "/*") {
isAsteriskComment = true
break
}
}
// split lines, and re-join together as a single
// paragraph, respecting double-newlines as
@@ -69,10 +76,12 @@ func extractDoc(node ast.Node, decl *ast.GenDecl) string {
}
for i, line := range outLines {
// Trim any extranous whitespace,
// for handling /*…*/-style comments,
// which have whitespace preserved in go/ast:
line = strings.TrimSpace(line)
if isAsteriskComment {
// Trim any extranous whitespace,
// for handling /*…*/-style comments,
// which have whitespace preserved in go/ast:
line = strings.TrimSpace(line)
}
// Respect that double-newline means
// actual newline:
@@ -82,8 +91,7 @@ func extractDoc(node ast.Node, decl *ast.GenDecl) string {
outLines[i] = line
}
}
return strings.Join(outLines, " ")
return strings.Join(outLines, "\n")
}
// PackageMarkers collects all the package-level marker values for the given package.

View File

@@ -149,6 +149,12 @@ func (r *Rule) ToRule() rbacv1.PolicyRule {
type Generator struct {
// RoleName sets the name of the generated ClusterRole.
RoleName string
// HeaderFile specifies the header text (e.g. license) to prepend to generated files.
HeaderFile string `marker:",optional"`
// Year specifies the year to substitute for " YEAR" in the header file.
Year string `marker:",optional"`
}
func (Generator) RegisterMarkers(into *markers.Registry) error {
@@ -263,5 +269,15 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
return nil
}
return ctx.WriteYAML("role.yaml", objs)
var headerText string
if g.HeaderFile != "" {
headerBytes, err := ctx.ReadFile(g.HeaderFile)
if err != nil {
return err
}
headerText = string(headerBytes)
}
headerText = strings.ReplaceAll(headerText, " YEAR", " "+g.Year)
return ctx.WriteYAML("role.yaml", headerText, objs, genall.WithTransform(genall.TransformRemoveCreationTimestamp))
}

View File

@@ -1,5 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright2019 The Kubernetes Authors.
@@ -37,6 +36,14 @@ func (Generator) Help() *markers.DefinitionHelp {
Summary: "sets the name of the generated ClusterRole.",
Details: "",
},
"HeaderFile": {
Summary: "specifies the header text (e.g. license) to prepend to generated files.",
Details: "",
},
"Year": {
Summary: "specifies the year to substitute for \" YEAR\" in the header file.",
Details: "",
},
},
}
}

View File

@@ -18,7 +18,7 @@ package schemapatcher
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"gopkg.in/yaml.v3"
@@ -335,7 +335,7 @@ func (e *partialCRD) setVersionedSchemata(newSchemata map[string]apiext.JSONSche
// minimally invasive. Returned CRDs are mapped by group-kind.
func crdsFromDirectory(ctx *genall.GenerationContext, dir string) (map[schema.GroupKind]*partialCRDSet, error) {
res := map[schema.GroupKind]*partialCRDSet{}
dirEntries, err := ioutil.ReadDir(dir)
dirEntries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}

View File

@@ -1,5 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright2019 The Kubernetes Authors.

View File

@@ -19,11 +19,12 @@ limitations under the License.
//
// The markers take the form:
//
// +kubebuilder:webhook:webhookVersions=<[]string>,failurePolicy=<string>,matchPolicy=<string>,groups=<[]string>,resources=<[]string>,verbs=<[]string>,versions=<[]string>,name=<string>,path=<string>,mutating=<bool>,sideEffects=<string>,admissionReviewVersions=<[]string>,reinvocationPolicy=<string>
// +kubebuilder:webhook:webhookVersions=<[]string>,failurePolicy=<string>,matchPolicy=<string>,groups=<[]string>,resources=<[]string>,verbs=<[]string>,versions=<[]string>,name=<string>,path=<string>,mutating=<bool>,sideEffects=<string>,timeoutSeconds=<int>,admissionReviewVersions=<[]string>,reinvocationPolicy=<string>
package webhook
import (
"fmt"
"sort"
"strings"
admissionregv1 "k8s.io/api/admissionregistration/v1"
@@ -80,6 +81,11 @@ type Config struct {
// If the value is "NoneOnDryRun", then the webhook is responsible for inspecting the "dryRun" property of the
// AdmissionReview sent in the request, and avoiding side effects if that value is "true."
SideEffects string `marker:",optional"`
// TimeoutSeconds allows configuring how long the API server should wait for a webhook to respond before treating the call as a failure.
// If the timeout expires before the webhook responds, the webhook call will be ignored or the API call will be rejected based on the failure policy.
// The timeout value must be between 1 and 30 seconds.
// The timeout for an admission webhook defaults to 10 seconds.
TimeoutSeconds int `marker:",optional"`
// Groups specifies the API groups that this webhook receives requests for.
Groups []string
@@ -102,7 +108,7 @@ type Config struct {
// are substituted for hyphens. For example, a validating webhook path for type
// batch.tutorial.kubebuilder.io/v1,Kind=CronJob would be
// /validate-batch-tutorial-kubebuilder-io-v1-cronjob
Path string
Path string `marker:"path,optional"`
// WebhookVersions specifies the target API versions of the {Mutating,Validating}WebhookConfiguration objects
// itself to generate. The only supported value is v1. Defaults to v1.
@@ -119,6 +125,14 @@ type Config struct {
// an object, and mutating webhooks can specify a reinvocationPolicy to control
// whether they are reinvoked as well.
ReinvocationPolicy string `marker:"reinvocationPolicy,optional"`
// URL allows mutating webhooks configuration to specify an external URL when generating
// the manifests, instead of using the internal service communication. Should be in format of
// https://address:port/path
// When this option is specified, the serviceConfig.Service is removed from webhook the manifest.
// The URL configuration should be between quotes.
// `url` cannot be specified when `path` is specified.
URL string `marker:"url,optional"`
}
// verbToAPIVariant converts a marker's verb to the proper value for the API.
@@ -151,13 +165,19 @@ func (c Config) ToMutatingWebhook() (admissionregv1.MutatingWebhook, error) {
return admissionregv1.MutatingWebhook{}, err
}
clientConfig, err := c.clientConfig()
if err != nil {
return admissionregv1.MutatingWebhook{}, err
}
return admissionregv1.MutatingWebhook{
Name: c.Name,
Rules: c.rules(),
FailurePolicy: c.failurePolicy(),
MatchPolicy: matchPolicy,
ClientConfig: c.clientConfig(),
ClientConfig: clientConfig,
SideEffects: c.sideEffects(),
TimeoutSeconds: c.timeoutSeconds(),
AdmissionReviewVersions: c.AdmissionReviewVersions,
ReinvocationPolicy: c.reinvocationPolicy(),
}, nil
@@ -174,13 +194,19 @@ func (c Config) ToValidatingWebhook() (admissionregv1.ValidatingWebhook, error)
return admissionregv1.ValidatingWebhook{}, err
}
clientConfig, err := c.clientConfig()
if err != nil {
return admissionregv1.ValidatingWebhook{}, err
}
return admissionregv1.ValidatingWebhook{
Name: c.Name,
Rules: c.rules(),
FailurePolicy: c.failurePolicy(),
MatchPolicy: matchPolicy,
ClientConfig: c.clientConfig(),
ClientConfig: clientConfig,
SideEffects: c.sideEffects(),
TimeoutSeconds: c.timeoutSeconds(),
AdmissionReviewVersions: c.AdmissionReviewVersions,
}, nil
}
@@ -243,15 +269,27 @@ func (c Config) matchPolicy() (*admissionregv1.MatchPolicyType, error) {
}
// clientConfig returns the client config for a webhook.
func (c Config) clientConfig() admissionregv1.WebhookClientConfig {
path := c.Path
return admissionregv1.WebhookClientConfig{
Service: &admissionregv1.ServiceReference{
Name: "webhook-service",
Namespace: "system",
Path: &path,
},
func (c Config) clientConfig() (admissionregv1.WebhookClientConfig, error) {
if (c.Path != "" && c.URL != "") || (c.Path == "" && c.URL == "") {
return admissionregv1.WebhookClientConfig{}, fmt.Errorf("`url` or `path` markers are required and mutually exclusive")
}
path := c.Path
if path != "" {
return admissionregv1.WebhookClientConfig{
Service: &admissionregv1.ServiceReference{
Name: "webhook-service",
Namespace: "system",
Path: &path,
},
}, nil
}
url := c.URL
return admissionregv1.WebhookClientConfig{
URL: &url,
}, nil
}
// sideEffects returns the sideEffects config for a webhook.
@@ -272,6 +310,15 @@ func (c Config) sideEffects() *admissionregv1.SideEffectClass {
return &sideEffects
}
// timeoutSeconds returns the timeoutSeconds config for a webhook.
func (c Config) timeoutSeconds() *int32 {
if c.TimeoutSeconds != 0 {
timeoutSeconds := int32(c.TimeoutSeconds)
return &timeoutSeconds
}
return nil
}
// reinvocationPolicy returns the reinvocationPolicy config for a mutating webhook.
func (c Config) reinvocationPolicy() *admissionregv1.ReinvocationPolicyType {
var reinvocationPolicy admissionregv1.ReinvocationPolicyType
@@ -304,7 +351,13 @@ func (c Config) webhookVersions() ([]string, error) {
// +controllertools:marker:generateHelp
// Generator generates (partial) {Mutating,Validating}WebhookConfiguration objects.
type Generator struct{}
type Generator struct {
// HeaderFile specifies the header text (e.g. license) to prepend to generated files.
HeaderFile string `marker:",optional"`
// Year specifies the year to substitute for " YEAR" in the header file.
Year string `marker:",optional"`
}
func (Generator) RegisterMarkers(into *markers.Registry) error {
if err := into.Register(ConfigDefinition); err != nil {
@@ -314,7 +367,7 @@ func (Generator) RegisterMarkers(into *markers.Registry) error {
return nil
}
func (Generator) Generate(ctx *genall.GenerationContext) error {
func (g Generator) Generate(ctx *genall.GenerationContext) error {
supportedWebhookVersions := supportedWebhookVersions()
mutatingCfgs := make(map[string][]admissionregv1.MutatingWebhook, len(supportedWebhookVersions))
validatingCfgs := make(map[string][]admissionregv1.ValidatingWebhook, len(supportedWebhookVersions))
@@ -324,7 +377,12 @@ func (Generator) Generate(ctx *genall.GenerationContext) error {
root.AddError(err)
}
for _, cfg := range markerSet[ConfigDefinition.Name] {
cfgs := markerSet[ConfigDefinition.Name]
sort.SliceStable(cfgs, func(i, j int) bool {
return cfgs[i].(Config).Name < cfgs[j].(Config).Name
})
for _, cfg := range cfgs {
cfg := cfg.(Config)
webhookVersions, err := cfg.webhookVersions()
if err != nil {
@@ -369,6 +427,11 @@ func (Generator) Generate(ctx *genall.GenerationContext) error {
if err := checkSideEffectsForV1(objRaw.Webhooks[i].SideEffects); err != nil {
return err
}
// TimeoutSeconds must be nil or between 1 and 30 seconds, otherwise,
// return an error
if err := checkTimeoutSeconds(objRaw.Webhooks[i].TimeoutSeconds); err != nil {
return err
}
// AdmissionReviewVersions is required in admissionregistration/v1, if this is not set,
// return an error
if len(objRaw.Webhooks[i].AdmissionReviewVersions) == 0 {
@@ -395,6 +458,11 @@ func (Generator) Generate(ctx *genall.GenerationContext) error {
if err := checkSideEffectsForV1(objRaw.Webhooks[i].SideEffects); err != nil {
return err
}
// TimeoutSeconds must be nil or between 1 and 30 seconds, otherwise,
// return an error
if err := checkTimeoutSeconds(objRaw.Webhooks[i].TimeoutSeconds); err != nil {
return err
}
// AdmissionReviewVersions is required in admissionregistration/v1, if this is not set,
// return an error
if len(objRaw.Webhooks[i].AdmissionReviewVersions) == 0 {
@@ -405,6 +473,16 @@ func (Generator) Generate(ctx *genall.GenerationContext) error {
}
}
var headerText string
if g.HeaderFile != "" {
headerBytes, err := ctx.ReadFile(g.HeaderFile)
if err != nil {
return err
}
headerText = string(headerBytes)
}
headerText = strings.ReplaceAll(headerText, " YEAR", " "+g.Year)
for k, v := range versionedWebhooks {
var fileName string
if k == defaultWebhookVersion {
@@ -412,7 +490,7 @@ func (Generator) Generate(ctx *genall.GenerationContext) error {
} else {
fileName = fmt.Sprintf("manifests.%s.yaml", k)
}
if err := ctx.WriteYAML(fileName, v); err != nil {
if err := ctx.WriteYAML(fileName, headerText, v, genall.WithTransform(genall.TransformRemoveCreationTimestamp)); err != nil {
return err
}
}
@@ -429,3 +507,10 @@ func checkSideEffectsForV1(sideEffects *admissionregv1.SideEffectClass) error {
}
return nil
}
func checkTimeoutSeconds(timeoutSeconds *int32) error {
if timeoutSeconds != nil && (*timeoutSeconds < 1 || *timeoutSeconds > 30) {
return fmt.Errorf("TimeoutSeconds must be between 1 and 30 seconds")
}
return nil
}

View File

@@ -1,5 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright2019 The Kubernetes Authors.
@@ -49,6 +48,10 @@ func (Config) Help() *markers.DefinitionHelp {
Summary: "specify whether calling the webhook will have side effects. This has an impact on dry runs and `kubectl diff`: if the sideEffect is \"Unknown\" (the default) or \"Some\", then the API server will not call the webhook on a dry-run request and fails instead. If the value is \"None\", then the webhook has no side effects and the API server will call it on dry-run. If the value is \"NoneOnDryRun\", then the webhook is responsible for inspecting the \"dryRun\" property of the AdmissionReview sent in the request, and avoiding side effects if that value is \"true.\"",
Details: "",
},
"TimeoutSeconds": {
Summary: "allows configuring how long the API server should wait for a webhook to respond before treating the call as a failure. If the timeout expires before the webhook responds, the webhook call will be ignored or the API call will be rejected based on the failure policy. The timeout value must be between 1 and 30 seconds. The timeout for an admission webhook defaults to 10 seconds.",
Details: "",
},
"Groups": {
Summary: "specifies the API groups that this webhook receives requests for.",
Details: "",
@@ -96,6 +99,15 @@ func (Generator) Help() *markers.DefinitionHelp {
Summary: "generates (partial) {Mutating,Validating}WebhookConfiguration objects.",
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{},
FieldHelp: map[string]markers.DetailedHelp{
"HeaderFile": {
Summary: "specifies the header text (e.g. license) to prepend to generated files.",
Details: "",
},
"Year": {
Summary: "specifies the year to substitute for \" YEAR\" in the header file.",
Details: "",
},
},
}
}