github.com/caseydavenport/controller-tools@v0.2.6-0.20200519183242-e8a18b1a6750/pkg/webhook/parser.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package webhook contains libraries for generating webhookconfig manifests
    18  // from markers in Go source files.
    19  //
    20  // The markers take the form:
    21  //
    22  //  +kubebuilder:webhook:failurePolicy=<string>,matchPolicy=<string>,groups=<[]string>,resources=<[]string>,verbs=<[]string>,versions=<[]string>,name=<string>,path=<string>,mutating=<bool>,sideEffects=<string>
    23  package webhook
    24  
    25  import (
    26  	"fmt"
    27  	"strings"
    28  
    29  	admissionreg "k8s.io/api/admissionregistration/v1beta1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  
    32  	"sigs.k8s.io/controller-tools/pkg/genall"
    33  	"sigs.k8s.io/controller-tools/pkg/markers"
    34  )
    35  
    36  var (
    37  	// ConfigDefinition s a marker for defining Webhook manifests.
    38  	// Call ToWebhook on the value to get a Kubernetes Webhook.
    39  	ConfigDefinition = markers.Must(markers.MakeDefinition("kubebuilder:webhook", markers.DescribesPackage, Config{}))
    40  )
    41  
    42  // +controllertools:marker:generateHelp:category=Webhook
    43  
    44  // Config specifies how a webhook should be served.
    45  //
    46  // It specifies only the details that are intrinsic to the application serving
    47  // it (e.g. the resources it can handle, or the path it serves on).
    48  type Config struct {
    49  	// Mutating marks this as a mutating webhook (it's validating only if false)
    50  	//
    51  	// Mutating webhooks are allowed to change the object in their response,
    52  	// and are called *before* all validating webhooks.  Mutating webhooks may
    53  	// choose to reject an object, similarly to a validating webhook.
    54  	Mutating bool
    55  	// FailurePolicy specifies what should happen if the API server cannot reach the webhook.
    56  	//
    57  	// It may be either "ignore" (to skip the webhook and continue on) or "fail" (to reject
    58  	// the object in question).
    59  	FailurePolicy string
    60  	// MatchPolicy defines how the "rules" list is used to match incoming requests.
    61  	// Allowed values are "Exact" (match only if it exactly matches the specified rule)
    62  	// or "Equivalent" (match a request if it modifies a resource listed in rules, even via another API group or version).
    63  	MatchPolicy string `marker:",optional"`
    64  	// SideEffects specify whether calling the webhook will have side effects.
    65  	// This has an impact on dry runs and `kubectl diff`: if the sideEffect is "Unknown" (the default) or "Some", then
    66  	// the API server will not call the webhook on a dry-run request and fails instead.
    67  	// If the value is "None", then the webhook has no side effects and the API server will call it on dry-run.
    68  	// If the value is "NoneOnDryRun", then the webhook is responsible for inspecting the "dryRun" property of the
    69  	// AdmissionReview sent in the request, and avoiding side effects if that value is "true."
    70  	SideEffects string `marker:",optional"`
    71  
    72  	// Groups specifies the API groups that this webhook receives requests for.
    73  	Groups []string
    74  	// Resources specifies the API resources that this webhook receives requests for.
    75  	Resources []string
    76  	// Verbs specifies the Kubernetes API verbs that this webhook receives requests for.
    77  	//
    78  	// Only modification-like verbs may be specified.
    79  	// May be "create", "update", "delete", "connect", or "*" (for all).
    80  	Verbs []string
    81  	// Versions specifies the API versions that this webhook receives requests for.
    82  	Versions []string
    83  
    84  	// Name indicates the name of this webhook configuration. Should be a domain with at least three segments separated by dots
    85  	Name string
    86  
    87  	// Path specifies that path that the API server should connect to this webhook on. Must be
    88  	// prefixed with a '/validate-' or '/mutate-' depending on the type, and followed by
    89  	// $GROUP-$VERSION-$KIND where all values are lower-cased and the periods in the group
    90  	// are substituted for hyphens. For example, a validating webhook path for type
    91  	// batch.tutorial.kubebuilder.io/v1,Kind=CronJob would be
    92  	// /validate-batch-tutorial-kubebuilder-io-v1-cronjob
    93  	Path string
    94  }
    95  
    96  // verbToAPIVariant converts a marker's verb to the proper value for the API.
    97  // Unrecognized verbs are passed through.
    98  func verbToAPIVariant(verbRaw string) admissionreg.OperationType {
    99  	switch strings.ToLower(verbRaw) {
   100  	case strings.ToLower(string(admissionreg.Create)):
   101  		return admissionreg.Create
   102  	case strings.ToLower(string(admissionreg.Update)):
   103  		return admissionreg.Update
   104  	case strings.ToLower(string(admissionreg.Delete)):
   105  		return admissionreg.Delete
   106  	case strings.ToLower(string(admissionreg.Connect)):
   107  		return admissionreg.Connect
   108  	case strings.ToLower(string(admissionreg.OperationAll)):
   109  		return admissionreg.OperationAll
   110  	default:
   111  		return admissionreg.OperationType(verbRaw)
   112  	}
   113  }
   114  
   115  // ToMutatingWebhook converts this rule to its Kubernetes API form.
   116  func (c Config) ToMutatingWebhook() (admissionreg.MutatingWebhook, error) {
   117  	if !c.Mutating {
   118  		return admissionreg.MutatingWebhook{}, fmt.Errorf("%s is a validating webhook", c.Name)
   119  	}
   120  
   121  	matchPolicy, err := c.matchPolicy()
   122  	if err != nil {
   123  		return admissionreg.MutatingWebhook{}, err
   124  	}
   125  
   126  	return admissionreg.MutatingWebhook{
   127  		Name:          c.Name,
   128  		Rules:         c.rules(),
   129  		FailurePolicy: c.failurePolicy(),
   130  		MatchPolicy:   matchPolicy,
   131  		ClientConfig:  c.clientConfig(),
   132  		SideEffects:   c.sideEffects(),
   133  	}, nil
   134  }
   135  
   136  // ToValidatingWebhook converts this rule to its Kubernetes API form.
   137  func (c Config) ToValidatingWebhook() (admissionreg.ValidatingWebhook, error) {
   138  	if c.Mutating {
   139  		return admissionreg.ValidatingWebhook{}, fmt.Errorf("%s is a mutating webhook", c.Name)
   140  	}
   141  
   142  	matchPolicy, err := c.matchPolicy()
   143  	if err != nil {
   144  		return admissionreg.ValidatingWebhook{}, err
   145  	}
   146  
   147  	return admissionreg.ValidatingWebhook{
   148  		Name:          c.Name,
   149  		Rules:         c.rules(),
   150  		FailurePolicy: c.failurePolicy(),
   151  		MatchPolicy:   matchPolicy,
   152  		ClientConfig:  c.clientConfig(),
   153  		SideEffects:   c.sideEffects(),
   154  	}, nil
   155  }
   156  
   157  // rules returns the configuration of what operations on what
   158  // resources/subresources a webhook should care about.
   159  func (c Config) rules() []admissionreg.RuleWithOperations {
   160  	whConfig := admissionreg.RuleWithOperations{
   161  		Rule: admissionreg.Rule{
   162  			APIGroups:   c.Groups,
   163  			APIVersions: c.Versions,
   164  			Resources:   c.Resources,
   165  		},
   166  		Operations: make([]admissionreg.OperationType, len(c.Verbs)),
   167  	}
   168  
   169  	for i, verbRaw := range c.Verbs {
   170  		whConfig.Operations[i] = verbToAPIVariant(verbRaw)
   171  	}
   172  
   173  	// fix the group names, since letting people type "core" is nice
   174  	for i, group := range whConfig.APIGroups {
   175  		if group == "core" {
   176  			whConfig.APIGroups[i] = ""
   177  		}
   178  	}
   179  
   180  	return []admissionreg.RuleWithOperations{whConfig}
   181  }
   182  
   183  // failurePolicy converts the string value to the proper value for the API.
   184  // Unrecognized values are passed through.
   185  func (c Config) failurePolicy() *admissionreg.FailurePolicyType {
   186  	var failurePolicy admissionreg.FailurePolicyType
   187  	switch strings.ToLower(c.FailurePolicy) {
   188  	case strings.ToLower(string(admissionreg.Ignore)):
   189  		failurePolicy = admissionreg.Ignore
   190  	case strings.ToLower(string(admissionreg.Fail)):
   191  		failurePolicy = admissionreg.Fail
   192  	default:
   193  		failurePolicy = admissionreg.FailurePolicyType(c.FailurePolicy)
   194  	}
   195  	return &failurePolicy
   196  }
   197  
   198  // matchPolicy converts the string value to the proper value for the API.
   199  func (c Config) matchPolicy() (*admissionreg.MatchPolicyType, error) {
   200  	var matchPolicy admissionreg.MatchPolicyType
   201  	switch strings.ToLower(c.MatchPolicy) {
   202  	case strings.ToLower(string(admissionreg.Exact)):
   203  		matchPolicy = admissionreg.Exact
   204  	case strings.ToLower(string(admissionreg.Equivalent)):
   205  		matchPolicy = admissionreg.Equivalent
   206  	case "":
   207  		return nil, nil
   208  	default:
   209  		return nil, fmt.Errorf("unknown value %q for matchPolicy", c.MatchPolicy)
   210  	}
   211  	return &matchPolicy, nil
   212  }
   213  
   214  // clientConfig returns the client config for a webhook.
   215  func (c Config) clientConfig() admissionreg.WebhookClientConfig {
   216  	path := c.Path
   217  	return admissionreg.WebhookClientConfig{
   218  		Service: &admissionreg.ServiceReference{
   219  			Name:      "webhook-service",
   220  			Namespace: "system",
   221  			Path:      &path,
   222  		},
   223  		// OpenAPI marks the field as required before 1.13 because of a bug that got fixed in
   224  		// https://github.com/kubernetes/api/commit/e7d9121e9ffd63cea0288b36a82bcc87b073bd1b
   225  		// Put "\n" as an placeholder as a workaround til 1.13+ is almost everywhere.
   226  		CABundle: []byte("\n"),
   227  	}
   228  }
   229  
   230  // sideEffects returns the sideEffects config for a webhook.
   231  func (c Config) sideEffects() *admissionreg.SideEffectClass {
   232  	var sideEffects admissionreg.SideEffectClass
   233  	switch strings.ToLower(c.SideEffects) {
   234  	case strings.ToLower(string(admissionreg.SideEffectClassNone)):
   235  		sideEffects = admissionreg.SideEffectClassNone
   236  	case strings.ToLower(string(admissionreg.SideEffectClassNoneOnDryRun)):
   237  		sideEffects = admissionreg.SideEffectClassNoneOnDryRun
   238  	case strings.ToLower(string(admissionreg.SideEffectClassSome)):
   239  		sideEffects = admissionreg.SideEffectClassSome
   240  	case "":
   241  		return nil
   242  	default:
   243  		return nil
   244  	}
   245  	return &sideEffects
   246  }
   247  
   248  // +controllertools:marker:generateHelp
   249  
   250  // Generator generates (partial) {Mutating,Validating}WebhookConfiguration objects.
   251  type Generator struct{}
   252  
   253  func (Generator) RegisterMarkers(into *markers.Registry) error {
   254  	if err := into.Register(ConfigDefinition); err != nil {
   255  		return err
   256  	}
   257  	into.AddHelp(ConfigDefinition, Config{}.Help())
   258  	return nil
   259  }
   260  
   261  func (Generator) Generate(ctx *genall.GenerationContext) error {
   262  	var mutatingCfgs []admissionreg.MutatingWebhook
   263  	var validatingCfgs []admissionreg.ValidatingWebhook
   264  	for _, root := range ctx.Roots {
   265  		markerSet, err := markers.PackageMarkers(ctx.Collector, root)
   266  		if err != nil {
   267  			root.AddError(err)
   268  		}
   269  
   270  		for _, cfg := range markerSet[ConfigDefinition.Name] {
   271  			cfg := cfg.(Config)
   272  			if cfg.Mutating {
   273  				w, _ := cfg.ToMutatingWebhook()
   274  				mutatingCfgs = append(mutatingCfgs, w)
   275  			} else {
   276  				w, _ := cfg.ToValidatingWebhook()
   277  				validatingCfgs = append(validatingCfgs, w)
   278  			}
   279  		}
   280  	}
   281  
   282  	var objs []interface{}
   283  	if len(mutatingCfgs) > 0 {
   284  		objs = append(objs, &admissionreg.MutatingWebhookConfiguration{
   285  			TypeMeta: metav1.TypeMeta{
   286  				Kind:       "MutatingWebhookConfiguration",
   287  				APIVersion: admissionreg.SchemeGroupVersion.String(),
   288  			},
   289  			ObjectMeta: metav1.ObjectMeta{
   290  				Name: "mutating-webhook-configuration",
   291  			},
   292  			Webhooks: mutatingCfgs,
   293  		})
   294  	}
   295  
   296  	if len(validatingCfgs) > 0 {
   297  		objs = append(objs, &admissionreg.ValidatingWebhookConfiguration{
   298  			TypeMeta: metav1.TypeMeta{
   299  				Kind:       "ValidatingWebhookConfiguration",
   300  				APIVersion: admissionreg.SchemeGroupVersion.String(),
   301  			},
   302  			ObjectMeta: metav1.ObjectMeta{
   303  				Name: "validating-webhook-configuration",
   304  			},
   305  			Webhooks: validatingCfgs,
   306  		})
   307  
   308  	}
   309  
   310  	if len(objs) > 0 {
   311  		return ctx.WriteYAML("manifests.yaml", objs...)
   312  	}
   313  
   314  	return nil
   315  }