use istio client-go library instead of knative (#1661)

use istio client-go library instead of knative
bump kubernetes dependency version
change code coverage to codecov
This commit is contained in:
zryfish
2019-12-13 11:26:18 +08:00
committed by GitHub
parent f249a6e081
commit ea88c8803d
2071 changed files with 354531 additions and 108336 deletions

View File

@@ -1,89 +0,0 @@
/*
Copyright 2018 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 webhook
import (
"errors"
"fmt"
"regexp"
"strings"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// admissionWebhook contains bits needed for generating a admissionWebhook Configuration
type admissionWebhook struct {
// name is the name of the webhook
name string
// typ is the webhook type, i.e. mutating, validating
typ webhookType
// path is the path this webhook will serve.
path string
// rules maps to the rules field in admissionregistrationv1beta1.admissionWebhook
rules []admissionregistrationv1beta1.RuleWithOperations
// failurePolicy maps to the failurePolicy field in admissionregistrationv1beta1.admissionWebhook
// This optional. If not set, will be defaulted to Ignore (fail-open) by the server.
// More details: https://github.com/kubernetes/api/blob/f5c295feaba2cbc946f0bbb8b535fc5f6a0345ee/admissionregistration/v1beta1/types.go#L144-L147
failurePolicy *admissionregistrationv1beta1.FailurePolicyType
// namespaceSelector maps to the namespaceSelector field in admissionregistrationv1beta1.admissionWebhook
// This optional.
namespaceSelector *metav1.LabelSelector
}
func (w *admissionWebhook) setDefaults() {
if len(w.path) == 0 {
if len(w.rules) == 0 || len(w.rules[0].Resources) == 0 {
// can't do defaulting, skip it.
return
}
if w.typ == mutatingWebhook {
w.path = "/mutate-" + w.rules[0].Resources[0]
} else if w.typ == validatingWebhook {
w.path = "/validate-" + w.rules[0].Resources[0]
}
}
if len(w.name) == 0 {
reg := regexp.MustCompile("[^a-zA-Z0-9]+")
processedPath := strings.ToLower(reg.ReplaceAllString(w.path, ""))
w.name = processedPath + ".example.com"
}
}
var _ webhook = &admissionWebhook{}
// GetType returns the type of the webhook.
func (w *admissionWebhook) GetType() webhookType {
return w.typ
}
// Validate validates if the webhook is valid.
func (w *admissionWebhook) Validate() error {
if len(w.rules) == 0 {
return errors.New("field rules should not be empty")
}
if len(w.name) == 0 {
return errors.New("field name should not be empty")
}
if w.typ != mutatingWebhook && w.typ != validatingWebhook {
return fmt.Errorf("unsupported Type: %v, only mutatingWebhook and validatingWebhook are supported", w.typ)
}
if len(w.path) == 0 {
return errors.New("field path should not be empty")
}
return nil
}

View File

@@ -1,334 +0,0 @@
/*
Copyright 2018 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 webhook
import (
"errors"
"net"
"net/url"
"path"
"sort"
"strconv"
"k8s.io/api/admissionregistration/v1beta1"
admissionregistration "k8s.io/api/admissionregistration/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
apitypes "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
)
type generatorOptions struct {
// webhooks maps a path to a webhoook.
webhooks map[string]webhook
// port is the port number that the server will serve.
// It will be defaulted to 443 if unspecified.
port int32
// certDir is the directory that contains the server key and certificate.
certDir string
// mutatingWebhookConfigName is the name that used for creating the MutatingWebhookConfiguration object.
mutatingWebhookConfigName string
// validatingWebhookConfigName is the name that used for creating the ValidatingWebhookConfiguration object.
validatingWebhookConfigName string
// secret is the location for storing the certificate for the admission server.
// The server should have permission to create a secret in the namespace.
secret *apitypes.NamespacedName
// service is a k8s service fronting the webhook server pod(s).
// One and only one of service and host can be set.
// This maps to field .Webhooks.ClientConfig.Service
// https://github.com/kubernetes/api/blob/183f3326a9353bd6d41430fc80f96259331d029c/admissionregistration/v1beta1/types.go#L260
service *service
// host is the host name of .Webhooks.ClientConfig.URL
// https://github.com/kubernetes/api/blob/183f3326a9353bd6d41430fc80f96259331d029c/admissionregistration/v1beta1/types.go#L250
// One and only one of service and host can be set.
// If neither service nor host is unspecified, host will be defaulted to "localhost".
host *string
}
// service contains information for creating a Service
type service struct {
// name of the Service
name string
// namespace of the Service
namespace string
// selectors is the selector of the Service.
// This must select the pods that runs this webhook server.
selectors map[string]string
}
// setDefaults does defaulting for the generatorOptions.
func (o *generatorOptions) setDefaults() {
if o.webhooks == nil {
o.webhooks = map[string]webhook{}
}
if o.port <= 0 {
o.port = 443
}
if len(o.certDir) == 0 {
o.certDir = path.Join("/tmp", "k8s-webhook-server", "serving-certs")
}
if len(o.mutatingWebhookConfigName) == 0 {
o.mutatingWebhookConfigName = "mutating-webhook-configuration"
}
if len(o.validatingWebhookConfigName) == 0 {
o.validatingWebhookConfigName = "validating-webhook-configuration"
}
if o.host == nil && o.service == nil {
varString := "localhost"
o.host = &varString
}
}
// Generate creates the AdmissionWebhookConfiguration objects and Service if any.
// It also provisions the certificate for the admission server.
func (o *generatorOptions) Generate() ([]runtime.Object, error) {
// do defaulting if necessary
o.setDefaults()
webhookConfigurations, err := o.whConfigs()
if err != nil {
return nil, err
}
svc := o.getService()
objects := append(webhookConfigurations, svc)
return objects, nil
}
// whConfigs creates a mutatingWebhookConfiguration and(or) a validatingWebhookConfiguration.
func (o *generatorOptions) whConfigs() ([]runtime.Object, error) {
for _, webhook := range o.webhooks {
if err := webhook.Validate(); err != nil {
return nil, err
}
}
objs := []runtime.Object{}
mutatingWH, err := o.mutatingWHConfig()
if err != nil {
return nil, err
}
if mutatingWH != nil {
objs = append(objs, mutatingWH)
}
validatingWH, err := o.validatingWHConfigs()
if err != nil {
return nil, err
}
if validatingWH != nil {
objs = append(objs, validatingWH)
}
return objs, nil
}
// mutatingWHConfig creates mutatingWebhookConfiguration.
func (o *generatorOptions) mutatingWHConfig() (runtime.Object, error) {
mutatingWebhooks := []v1beta1.Webhook{}
for path, webhook := range o.webhooks {
if webhook.GetType() != mutatingWebhook {
continue
}
aw := webhook.(*admissionWebhook)
wh, err := o.admissionWebhook(path, aw)
if err != nil {
return nil, err
}
mutatingWebhooks = append(mutatingWebhooks, *wh)
}
sort.Slice(mutatingWebhooks, func(i, j int) bool {
return mutatingWebhooks[i].Name < mutatingWebhooks[j].Name
})
if len(mutatingWebhooks) > 0 {
return &admissionregistration.MutatingWebhookConfiguration{
TypeMeta: metav1.TypeMeta{
APIVersion: metav1.GroupVersion{Group: admissionregistration.GroupName, Version: "v1beta1"}.String(),
Kind: "MutatingWebhookConfiguration",
},
ObjectMeta: metav1.ObjectMeta{
Name: o.mutatingWebhookConfigName,
Annotations: map[string]string{
// TODO(DirectXMan12): Change the annotation to the format that cert-manager decides to use.
"alpha.admissionwebhook.cert-manager.io": "true",
},
},
Webhooks: mutatingWebhooks,
}, nil
}
return nil, nil
}
func (o *generatorOptions) validatingWHConfigs() (runtime.Object, error) {
validatingWebhooks := []v1beta1.Webhook{}
for path, webhook := range o.webhooks {
var aw *admissionWebhook
if webhook.GetType() != validatingWebhook {
continue
}
aw = webhook.(*admissionWebhook)
wh, err := o.admissionWebhook(path, aw)
if err != nil {
return nil, err
}
validatingWebhooks = append(validatingWebhooks, *wh)
}
sort.Slice(validatingWebhooks, func(i, j int) bool {
return validatingWebhooks[i].Name < validatingWebhooks[j].Name
})
if len(validatingWebhooks) > 0 {
return &admissionregistration.ValidatingWebhookConfiguration{
TypeMeta: metav1.TypeMeta{
APIVersion: metav1.GroupVersion{Group: admissionregistration.GroupName, Version: "v1beta1"}.String(),
Kind: "ValidatingWebhookConfiguration",
},
ObjectMeta: metav1.ObjectMeta{
Name: o.validatingWebhookConfigName,
Annotations: map[string]string{
// TODO(DirectXMan12): Change the annotation to the format that cert-manager decides to use.
"alpha.admissionwebhook.cert-manager.io": "true",
},
},
Webhooks: validatingWebhooks,
}, nil
}
return nil, nil
}
func (o *generatorOptions) admissionWebhook(path string, wh *admissionWebhook) (*admissionregistration.Webhook, error) {
if wh.namespaceSelector == nil && o.service != nil && len(o.service.namespace) > 0 {
wh.namespaceSelector = &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "control-plane",
Operator: metav1.LabelSelectorOpDoesNotExist,
},
},
}
}
webhook := &admissionregistration.Webhook{
Name: wh.name,
Rules: wh.rules,
FailurePolicy: wh.failurePolicy,
NamespaceSelector: wh.namespaceSelector,
}
cc, err := o.getClientConfigWithPath(path)
if err != nil {
return nil, err
}
webhook.ClientConfig = *cc
return webhook, nil
}
// getClientConfigWithPath constructs a WebhookClientConfig based on the server generatorOptions.
// It will use path to the set the path in WebhookClientConfig.
func (o *generatorOptions) getClientConfigWithPath(path string) (*admissionregistration.WebhookClientConfig, error) {
cc, err := o.getClientConfig()
if err != nil {
return nil, err
}
return cc, setPath(cc, path)
}
func (o *generatorOptions) getClientConfig() (*admissionregistration.WebhookClientConfig, error) {
if o.host != nil && o.service != nil {
return nil, errors.New("URL and service can't be set at the same time")
}
cc := &admissionregistration.WebhookClientConfig{
// Put an non-empty and not harmful CABundle here.
// Not doing this will cause the field
CABundle: []byte(`\n`),
}
if o.host != nil {
u := url.URL{
Scheme: "https",
Host: net.JoinHostPort(*o.host, strconv.Itoa(int(o.port))),
}
urlString := u.String()
cc.URL = &urlString
}
if o.service != nil {
cc.Service = &admissionregistration.ServiceReference{
Name: o.service.name,
Namespace: o.service.namespace,
// Path will be set later
}
}
return cc, nil
}
// setPath sets the path in the WebhookClientConfig.
func setPath(cc *admissionregistration.WebhookClientConfig, path string) error {
if cc.URL != nil {
u, err := url.Parse(*cc.URL)
if err != nil {
return err
}
u.Path = path
urlString := u.String()
cc.URL = &urlString
}
if cc.Service != nil {
cc.Service.Path = &path
}
return nil
}
// getService creates a corev1.Service object fronting the admission server.
func (o *generatorOptions) getService() runtime.Object {
if o.service == nil {
return nil
}
svc := &corev1.Service{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Service",
},
ObjectMeta: metav1.ObjectMeta{
Name: o.service.name,
Namespace: o.service.namespace,
Annotations: map[string]string{
// Secret here only need name, since it will be in the same namespace as the service.
// TODO(DirectXMan12): Change the annotation to the format that cert-manager decides to use.
"alpha.service.cert-manager.io/serving-cert-secret-name": o.secret.Name,
},
},
Spec: corev1.ServiceSpec{
Selector: o.service.selectors,
Ports: []corev1.ServicePort{
{
// When using service, kube-apiserver will send admission request to port 443.
Port: 443,
TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: o.port},
},
},
},
}
return svc
}

View File

@@ -1,151 +0,0 @@
/*
Copyright 2018 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 webhook
import (
"bytes"
"fmt"
"path"
"strings"
"text/template"
"github.com/ghodss/yaml"
"github.com/spf13/afero"
"sigs.k8s.io/controller-tools/pkg/internal/general"
)
// Options represent options for generating the webhook manifests.
type Options struct {
// WriterOptions specifies the input and output
WriterOptions
generatorOptions
}
// Generate generates RBAC manifests by parsing the RBAC annotations in Go source
// files specified in the input directory.
func Generate(o *Options) error {
if err := o.WriterOptions.Validate(); err != nil {
return err
}
err := general.ParseDir(o.InputDir, o.parseAnnotation)
if err != nil {
return fmt.Errorf("failed to parse the input dir: %v", err)
}
if len(o.webhooks) == 0 {
return nil
}
objs, err := o.Generate()
if err != nil {
return err
}
err = o.WriteObjectsToDisk(objs...)
if err != nil {
return err
}
return o.controllerManagerPatch()
}
func (o *Options) controllerManagerPatch() error {
var kustomizeLabelPatch = `apiVersion: apps/v1
kind: StatefulSet
metadata:
name: controller-manager
spec:
template:
metadata:
{{- with .Labels }}
labels:
{{ toYaml . | indent 8 }}
{{- end }}
spec:
containers:
- name: manager
ports:
- containerPort: {{ .Port }}
name: webhook-server
protocol: TCP
volumeMounts:
- mountPath: {{ .CertDir }}
name: cert
readOnly: true
volumes:
- name: cert
secret:
defaultMode: 420
secretName: {{ .SecretName }}
`
type KustomizeLabelPatch struct {
Labels map[string]string
SecretName string
Port int32
CertDir string
}
p := KustomizeLabelPatch{
Labels: o.service.selectors,
SecretName: o.secret.Name,
Port: o.port,
CertDir: o.certDir,
}
funcMap := template.FuncMap{
"toYaml": toYAML,
"indent": indent,
}
temp, err := template.New("kustomizeLabelPatch").Funcs(funcMap).Parse(kustomizeLabelPatch)
if err != nil {
return err
}
buf := bytes.NewBuffer(nil)
if err := temp.Execute(buf, p); err != nil {
return err
}
return afero.WriteFile(o.outFs, path.Join(o.PatchOutputDir, "manager_patch.yaml"), buf.Bytes(), 0644)
}
func toYAML(m map[string]string) (string, error) {
d, err := yaml.Marshal(m)
return string(d), err
}
func indent(n int, s string) (string, error) {
buf := bytes.NewBuffer(nil)
for _, elem := range strings.Split(s, "\n") {
for i := 0; i < n; i++ {
_, err := buf.WriteRune(' ')
if err != nil {
return "", err
}
}
_, err := buf.WriteString(elem)
if err != nil {
return "", err
}
_, err = buf.WriteRune('\n')
if err != nil {
return "", err
}
}
return strings.TrimRight(buf.String(), " \n"), nil
}

View File

@@ -14,223 +14,237 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Package webhook contains libraries for generating webhookconfig manifests
// from markers in Go source files.
//
// The markers take the form:
//
// +kubebuilder:webhook:failurePolicy=<string>,groups=<[]string>,resources=<[]string>,verbs=<[]string>,versions=<[]string>,name=<string>,path=<string>,mutating=<bool>
package webhook
import (
"errors"
"fmt"
"log"
"strconv"
"strings"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"sigs.k8s.io/controller-tools/pkg/internal/general"
)
admissionreg "k8s.io/api/admissionregistration/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
const webhookAnnotationPrefix = "kubebuilder:webhook"
"sigs.k8s.io/controller-tools/pkg/genall"
"sigs.k8s.io/controller-tools/pkg/markers"
)
var (
webhookTags = sets.NewString([]string{"groups", "versions", "resources", "verbs", "type", "name", "path", "failure-policy"}...)
serverTags = sets.NewString([]string{"port", "cert-dir", "service", "selector", "secret", "host", "mutating-webhook-config-name", "validating-webhook-config-name"}...)
// ConfigDefinition s a marker for defining Webhook manifests.
// Call ToWebhook on the value to get a Kubernetes Webhook.
ConfigDefinition = markers.Must(markers.MakeDefinition("kubebuilder:webhook", markers.DescribesPackage, Config{}))
)
// parseAnnotation parses webhook annotations
func (o *Options) parseAnnotation(commentText string) error {
webhookKVMap, serverKVMap := map[string]string{}, map[string]string{}
for _, comment := range strings.Split(commentText, "\n") {
comment := strings.TrimSpace(comment)
anno := general.GetAnnotation(comment, webhookAnnotationPrefix)
if len(anno) == 0 {
continue
}
for _, elem := range strings.Split(anno, ",") {
key, value, err := general.ParseKV(elem)
if err != nil {
log.Fatalf("// +kubebuilder:webhook: tags must be key value pairs. Example "+
"keys [groups=<group1;group2>,resources=<resource1;resource2>,verbs=<verb1;verb2>] "+
"Got string: [%s]", anno)
}
switch {
case webhookTags.Has(key):
webhookKVMap[key] = value
case serverTags.Has(key):
serverKVMap[key] = value
}
// +controllertools:marker:generateHelp:category=Webhook
// Config specifies how a webhook should be served.
//
// It specifies only the details that are intrinsic to the application serving
// it (e.g. the resources it can handle, or the path it serves on).
type Config struct {
// Mutating marks this as a mutating webhook (it's validating only if false)
//
// Mutating webhooks are allowed to change the object in their response,
// and are called *before* all validating webhooks. Mutating webhooks may
// choose to reject an object, similarly to a validating webhook.
Mutating bool
// FailurePolicy specifies what should happen if the API server cannot reach the webhook.
//
// It may be either "ignore" (to skip the webhook and continue on) or "fail" (to reject
// the object in question).
FailurePolicy string
// Groups specifies the API groups that this webhook receives requests for.
Groups []string
// Resources specifies the API resources that this webhook receives requests for.
Resources []string
// Verbs specifies the Kubernetes API verbs that this webhook receives requests for.
//
// Only modification-like verbs may be specified.
// May be "create", "update", "delete", "connect", or "*" (for all).
Verbs []string
// Versions specifies the API versions that this webhook receives requests for.
Versions []string
// Name indicates the name of this webhook configuration.
Name string
// Path specifies that path that the API server should connect to this webhook on.
Path string
}
// verbToAPIVariant converts a marker's verb to the proper value for the API.
// Unrecognized verbs are passed through.
func verbToAPIVariant(verbRaw string) admissionreg.OperationType {
switch strings.ToLower(verbRaw) {
case strings.ToLower(string(admissionreg.Create)):
return admissionreg.Create
case strings.ToLower(string(admissionreg.Update)):
return admissionreg.Update
case strings.ToLower(string(admissionreg.Delete)):
return admissionreg.Delete
case strings.ToLower(string(admissionreg.Connect)):
return admissionreg.Connect
case strings.ToLower(string(admissionreg.OperationAll)):
return admissionreg.OperationAll
default:
return admissionreg.OperationType(verbRaw)
}
}
// ToMutatingWebhook converts this rule to its Kubernetes API form.
func (c Config) ToMutatingWebhook() (admissionreg.MutatingWebhook, error) {
if !c.Mutating {
return admissionreg.MutatingWebhook{}, fmt.Errorf("%s is a validating webhook", c.Name)
}
return admissionreg.MutatingWebhook{
Name: c.Name,
Rules: c.rules(),
FailurePolicy: c.failurePolicy(),
ClientConfig: c.clientConfig(),
}, nil
}
// ToValidatingWebhook converts this rule to its Kubernetes API form.
func (c Config) ToValidatingWebhook() (admissionreg.ValidatingWebhook, error) {
if c.Mutating {
return admissionreg.ValidatingWebhook{}, fmt.Errorf("%s is a mutating webhook", c.Name)
}
return admissionreg.ValidatingWebhook{
Name: c.Name,
Rules: c.rules(),
FailurePolicy: c.failurePolicy(),
ClientConfig: c.clientConfig(),
}, nil
}
// rules returns the configuration of what operations on what
// resources/subresources a webhook should care about.
func (c Config) rules() []admissionreg.RuleWithOperations {
whConfig := admissionreg.RuleWithOperations{
Rule: admissionreg.Rule{
APIGroups: c.Groups,
APIVersions: c.Versions,
Resources: c.Resources,
},
Operations: make([]admissionreg.OperationType, len(c.Verbs)),
}
for i, verbRaw := range c.Verbs {
whConfig.Operations[i] = verbToAPIVariant(verbRaw)
}
// fix the group names, since letting people type "core" is nice
for i, group := range whConfig.APIGroups {
if group == "core" {
whConfig.APIGroups[i] = ""
}
}
if err := o.parseWebhookAnnotation(webhookKVMap); err != nil {
return []admissionreg.RuleWithOperations{whConfig}
}
// failurePolicy converts the string value to the proper value for the API.
// Unrecognized values are passed through.
func (c Config) failurePolicy() *admissionreg.FailurePolicyType {
var failurePolicy admissionreg.FailurePolicyType
switch strings.ToLower(c.FailurePolicy) {
case strings.ToLower(string(admissionreg.Ignore)):
failurePolicy = admissionreg.Ignore
case strings.ToLower(string(admissionreg.Fail)):
failurePolicy = admissionreg.Fail
default:
failurePolicy = admissionreg.FailurePolicyType(c.FailurePolicy)
}
return &failurePolicy
}
// clientConfig returns the client config for a webhook.
func (c Config) clientConfig() admissionreg.WebhookClientConfig {
path := c.Path
return admissionreg.WebhookClientConfig{
Service: &admissionreg.ServiceReference{
Name: "webhook-service",
Namespace: "system",
Path: &path,
},
// OpenAPI marks the field as required before 1.13 because of a bug that got fixed in
// https://github.com/kubernetes/api/commit/e7d9121e9ffd63cea0288b36a82bcc87b073bd1b
// Put "\n" as an placeholder as a workaround til 1.13+ is almost everywhere.
CABundle: []byte("\n"),
}
}
// +controllertools:marker:generateHelp
// Generator generates (partial) {Mutating,Validating}WebhookConfiguration objects.
type Generator struct{}
func (Generator) RegisterMarkers(into *markers.Registry) error {
if err := into.Register(ConfigDefinition); err != nil {
return err
}
return o.parseServerAnnotation(serverKVMap)
}
// parseWebhookAnnotation parses webhook annotations in the same comment group
// nolint: gocyclo
func (o *Options) parseWebhookAnnotation(kvMap map[string]string) error {
if len(kvMap) == 0 {
return nil
}
rule := admissionregistrationv1beta1.RuleWithOperations{}
w := &admissionWebhook{}
for key, value := range kvMap {
switch key {
case "groups":
values := strings.Split(value, ";")
normalized := []string{}
for _, v := range values {
if v == "core" {
normalized = append(normalized, "")
} else {
normalized = append(normalized, v)
}
}
rule.APIGroups = values
case "versions":
values := strings.Split(value, ";")
rule.APIVersions = values
case "resources":
values := strings.Split(value, ";")
rule.Resources = values
case "verbs":
values := strings.Split(value, ";")
var ops []admissionregistrationv1beta1.OperationType
for _, v := range values {
switch strings.ToLower(v) {
case strings.ToLower(string(admissionregistrationv1beta1.Create)):
ops = append(ops, admissionregistrationv1beta1.Create)
case strings.ToLower(string(admissionregistrationv1beta1.Update)):
ops = append(ops, admissionregistrationv1beta1.Update)
case strings.ToLower(string(admissionregistrationv1beta1.Delete)):
ops = append(ops, admissionregistrationv1beta1.Delete)
case strings.ToLower(string(admissionregistrationv1beta1.Connect)):
ops = append(ops, admissionregistrationv1beta1.Connect)
case strings.ToLower(string(admissionregistrationv1beta1.OperationAll)):
ops = append(ops, admissionregistrationv1beta1.OperationAll)
default:
return fmt.Errorf("unknown operation: %v", v)
}
}
rule.Operations = ops
case "type":
switch strings.ToLower(value) {
case "mutating":
w.typ = mutatingWebhook
case "validating":
w.typ = validatingWebhook
default:
return fmt.Errorf("unknown webhook type: %v", value)
}
case "name":
w.name = value
case "path":
w.path = value
case "failure-policy":
switch strings.ToLower(value) {
case strings.ToLower(string(admissionregistrationv1beta1.Ignore)):
fp := admissionregistrationv1beta1.Ignore
w.failurePolicy = &fp
case strings.ToLower(string(admissionregistrationv1beta1.Fail)):
fp := admissionregistrationv1beta1.Fail
w.failurePolicy = &fp
default:
return fmt.Errorf("unknown webhook failure policy: %v", value)
}
}
}
w.rules = []admissionregistrationv1beta1.RuleWithOperations{rule}
if o.webhooks == nil {
o.webhooks = map[string]webhook{}
}
o.webhooks[w.path] = w
into.AddHelp(ConfigDefinition, Config{}.Help())
return nil
}
// parseWebhookAnnotation parses webhook server annotations in the same comment group
// nolint: gocyclo
func (o *Options) parseServerAnnotation(kvMap map[string]string) error {
if len(kvMap) == 0 {
return nil
}
for key, value := range kvMap {
switch key {
case "port":
port, err := strconv.Atoi(value)
if err != nil {
return err
}
o.port = int32(port)
case "cert-dir":
o.certDir = value
case "service":
// format: <service=namespace:name>
split := strings.Split(value, ":")
if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 {
return fmt.Errorf("invalid service format: expect <namespace:name>, but got %q", value)
}
if o.service == nil {
o.service = &service{}
}
o.service.namespace = split[0]
o.service.name = split[1]
case "selector":
// selector of the service. Format: <selector=label1:value1;label2:value2>
split := strings.Split(value, ";")
if len(split) == 0 {
return fmt.Errorf("invalid selector format: expect <label1:value1;label2:value2>, but got %q", value)
}
if o.service == nil {
o.service = &service{}
}
for _, v := range split {
l := strings.Split(v, ":")
if len(l) != 2 || len(l[0]) == 0 || len(l[1]) == 0 {
return fmt.Errorf("invalid selector format: expect <label1:value1;label2:value2>, but got %q", value)
}
if o.service.selectors == nil {
o.service.selectors = map[string]string{}
}
o.service.selectors[l[0]] = l[1]
}
case "host":
if len(value) == 0 {
return errors.New("host should not be empty if specified")
}
o.host = &value
func (Generator) Generate(ctx *genall.GenerationContext) error {
var mutatingCfgs []admissionreg.MutatingWebhook
var validatingCfgs []admissionreg.ValidatingWebhook
for _, root := range ctx.Roots {
markerSet, err := markers.PackageMarkers(ctx.Collector, root)
if err != nil {
root.AddError(err)
}
case "mutating-webhook-config-name":
if len(value) == 0 {
return errors.New("mutating-webhook-config-name should not be empty if specified")
for _, cfg := range markerSet[ConfigDefinition.Name] {
cfg := cfg.(Config)
if cfg.Mutating {
w, _ := cfg.ToMutatingWebhook()
mutatingCfgs = append(mutatingCfgs, w)
} else {
w, _ := cfg.ToValidatingWebhook()
validatingCfgs = append(validatingCfgs, w)
}
o.mutatingWebhookConfigName = value
case "validating-webhook-config-name":
if len(value) == 0 {
return errors.New("validating-webhook-config-name should not be empty if specified")
}
o.validatingWebhookConfigName = value
case "secret":
// format: <secret=namespace:name>
split := strings.Split(value, ":")
if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 {
return fmt.Errorf("invalid secret format: expect <namespace:name>, but got %q", value)
}
if o.secret == nil {
o.secret = &types.NamespacedName{}
}
o.secret.Namespace = split[0]
o.secret.Name = split[1]
}
}
var objs []interface{}
if len(mutatingCfgs) > 0 {
objs = append(objs, &admissionreg.MutatingWebhookConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "MutatingWebhookConfiguration",
APIVersion: admissionreg.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "mutating-webhook-configuration",
},
Webhooks: mutatingCfgs,
})
}
if len(validatingCfgs) > 0 {
objs = append(objs, &admissionreg.ValidatingWebhookConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "ValidatingWebhookConfiguration",
APIVersion: admissionreg.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "validating-webhook-configuration",
},
Webhooks: validatingCfgs,
})
}
if err := ctx.WriteYAML("manifests.yaml", objs...); err != nil {
return err
}
return nil
}

View File

@@ -1,38 +0,0 @@
/*
Copyright 2018 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 webhook
// webhookType defines the type of a webhook
type webhookType int
const (
_ = iota
// mutatingWebhook represents mutating type webhook
mutatingWebhook webhookType = iota
// validatingWebhook represents validating type webhook
validatingWebhook
)
// webhook defines the basics that a webhook should support.
type webhook interface {
// GetType returns the Type of the webhook.
// e.g. mutating or validating
GetType() webhookType
// Validate validates if the webhook itself is valid.
// If invalid, a non-nil error will be returned.
Validate() error
}

View File

@@ -1,92 +0,0 @@
package webhook
import (
"bytes"
"fmt"
"path"
"path/filepath"
"github.com/ghodss/yaml"
"github.com/spf13/afero"
"k8s.io/apimachinery/pkg/runtime"
)
// WriterOptions specifies the input and output.
type WriterOptions struct {
InputDir string
OutputDir string
PatchOutputDir string
// inFs is filesystem to be used for reading input
inFs afero.Fs
// outFs is filesystem to be used for writing out the result
outFs afero.Fs
}
// SetDefaults sets up the default options for RBAC Manifest generator.
func (o *WriterOptions) SetDefaults() {
if o.inFs == nil {
o.inFs = afero.NewOsFs()
}
if o.outFs == nil {
o.outFs = afero.NewOsFs()
}
if len(o.InputDir) == 0 {
o.InputDir = filepath.Join(".", "pkg", "webhook")
}
if len(o.OutputDir) == 0 {
o.OutputDir = filepath.Join(".", "config", "webhook")
}
if len(o.PatchOutputDir) == 0 {
o.PatchOutputDir = filepath.Join(".", "config", "default")
}
}
// Validate validates the input options.
func (o *WriterOptions) Validate() error {
if _, err := o.inFs.Stat(o.InputDir); err != nil {
return fmt.Errorf("invalid input directory '%s' %v", o.InputDir, err)
}
return nil
}
// WriteObjectsToDisk writes object to the location specified in WriterOptions.
func (o *WriterOptions) WriteObjectsToDisk(objects ...runtime.Object) error {
exists, err := afero.DirExists(o.outFs, o.OutputDir)
if err != nil {
return err
}
if !exists {
err = o.outFs.MkdirAll(o.OutputDir, 0766)
if err != nil {
return err
}
}
var buf bytes.Buffer
isFirstObject := true
for _, obj := range objects {
if !isFirstObject {
_, err = buf.WriteString("---\n")
if err != nil {
return err
}
}
marshalled, err := yaml.Marshal(obj)
if err != nil {
return err
}
_, err = buf.Write(marshalled)
if err != nil {
return err
}
isFirstObject = false
}
err = afero.WriteFile(o.outFs, path.Join(o.OutputDir, "webhookmanifests.yaml"), buf.Bytes(), 0644)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,80 @@
// +build !ignore_autogenerated
/*
Copyright2019 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.
*/
// Code generated by helpgen. DO NOT EDIT.
package webhook
import (
"sigs.k8s.io/controller-tools/pkg/markers"
)
func (Config) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "Webhook",
DetailedHelp: markers.DetailedHelp{
Summary: "specifies how a webhook should be served. ",
Details: "It specifies only the details that are intrinsic to the application serving it (e.g. the resources it can handle, or the path it serves on).",
},
FieldHelp: map[string]markers.DetailedHelp{
"Mutating": markers.DetailedHelp{
Summary: "marks this as a mutating webhook (it's validating only if false) ",
Details: "Mutating webhooks are allowed to change the object in their response, and are called *before* all validating webhooks. Mutating webhooks may choose to reject an object, similarly to a validating webhook.",
},
"FailurePolicy": markers.DetailedHelp{
Summary: "specifies what should happen if the API server cannot reach the webhook. ",
Details: "It may be either \"ignore\" (to skip the webhook and continue on) or \"fail\" (to reject the object in question).",
},
"Groups": markers.DetailedHelp{
Summary: "specifies the API groups that this webhook receives requests for.",
Details: "",
},
"Resources": markers.DetailedHelp{
Summary: "specifies the API resources that this webhook receives requests for.",
Details: "",
},
"Verbs": markers.DetailedHelp{
Summary: "specifies the Kubernetes API verbs that this webhook receives requests for. ",
Details: "Only modification-like verbs may be specified. May be \"create\", \"update\", \"delete\", \"connect\", or \"*\" (for all).",
},
"Versions": markers.DetailedHelp{
Summary: "specifies the API versions that this webhook receives requests for.",
Details: "",
},
"Name": markers.DetailedHelp{
Summary: "indicates the name of this webhook configuration.",
Details: "",
},
"Path": markers.DetailedHelp{
Summary: "specifies that path that the API server should connect to this webhook on.",
Details: "",
},
},
}
}
func (Generator) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "",
DetailedHelp: markers.DetailedHelp{
Summary: "generates (partial) {Mutating,Validating}WebhookConfiguration objects.",
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
}