crossplane

Форк
0
185 строк · 6.4 Кб
1
/*
2
Copyright 2023 The Crossplane 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 xrd contains internal logic linked to the validation of the v1.CompositeResourceDefinition type.
18
package xrd
19

20
import (
21
	"context"
22
	"errors"
23
	"fmt"
24

25
	apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
26
	kerrors "k8s.io/apimachinery/pkg/api/errors"
27
	"k8s.io/apimachinery/pkg/runtime"
28
	ctrl "sigs.k8s.io/controller-runtime"
29
	"sigs.k8s.io/controller-runtime/pkg/client"
30
	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
31

32
	"github.com/crossplane/crossplane-runtime/pkg/controller"
33
	xperrors "github.com/crossplane/crossplane-runtime/pkg/errors"
34

35
	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
36
	"github.com/crossplane/crossplane/internal/xcrd"
37
)
38

39
// Error strings.
40
const (
41
	errNotCompositeResourceDefinition = "supplied object was not a CompositeResourceDefinition"
42

43
	errUnexpectedType = "unexpected type"
44
)
45

46
// SetupWebhookWithManager sets up the webhook with the manager.
47
func SetupWebhookWithManager(mgr ctrl.Manager, _ controller.Options) error {
48
	v := &validator{client: mgr.GetClient()}
49
	return ctrl.NewWebhookManagedBy(mgr).
50
		WithValidator(v).
51
		For(&v1.CompositeResourceDefinition{}).
52
		Complete()
53
}
54

55
type validator struct {
56
	client client.Client
57
}
58

59
func getAllCRDsForXRD(in *v1.CompositeResourceDefinition) (out []*apiextv1.CustomResourceDefinition, err error) {
60
	crd, err := xcrd.ForCompositeResource(in)
61
	if err != nil {
62
		return out, xperrors.Wrap(err, "cannot get CRD for Composite Resource")
63
	}
64
	out = append(out, crd)
65
	// if claim enabled, validate claim CRD
66
	if in.Spec.ClaimNames == nil {
67
		return out, nil
68
	}
69
	crdClaim, err := xcrd.ForCompositeResourceClaim(in)
70
	if err != nil {
71
		return out, xperrors.Wrap(err, "cannot get Claim CRD for Composite Claim")
72
	}
73
	out = append(out, crdClaim)
74
	return out, nil
75
}
76

77
// ValidateCreate validates a Composition.
78
func (v *validator) ValidateCreate(ctx context.Context, obj runtime.Object) (warns admission.Warnings, err error) {
79
	in, ok := obj.(*v1.CompositeResourceDefinition)
80
	if !ok {
81
		return nil, errors.New(errNotCompositeResourceDefinition)
82
	}
83
	validationWarns, validationErr := in.Validate()
84
	warns = append(warns, validationWarns...)
85
	if validationErr != nil {
86
		return validationWarns, validationErr.ToAggregate()
87
	}
88
	crds, err := getAllCRDsForXRD(in)
89
	if err != nil {
90
		return warns, xperrors.Wrap(err, "cannot get CRDs for CompositeResourceDefinition")
91
	}
92
	for _, crd := range crds {
93
		// Can't use validation.ValidateCustomResourceDefinition because it leads to dependency errors,
94
		// see https://github.com/kubernetes/apiextensions-apiserver/issues/59
95
		// if errs := validation.ValidateCustomResourceDefinition(ctx, crd); len(errs) != 0 {
96
		//	return warns, errors.Wrap(errs.ToAggregate(), "invalid CRD generated for CompositeResourceDefinition")
97
		//}
98
		if err := v.client.Create(ctx, crd, client.DryRunAll); err != nil {
99
			return warns, v.rewriteError(err, in, crd)
100
		}
101
	}
102

103
	return warns, nil
104
}
105

106
// ValidateUpdate implements the same logic as ValidateCreate.
107
func (v *validator) ValidateUpdate(ctx context.Context, old, new runtime.Object) (warns admission.Warnings, err error) {
108
	// Validate the update
109
	oldObj, ok := old.(*v1.CompositeResourceDefinition)
110
	if !ok {
111
		return nil, errors.New(errUnexpectedType)
112
	}
113
	newObj, ok := new.(*v1.CompositeResourceDefinition)
114
	if !ok {
115
		return nil, errors.New(errUnexpectedType)
116
	}
117
	// Validate the update
118
	validationWarns, validationErr := newObj.ValidateUpdate(oldObj)
119
	warns = append(warns, validationWarns...)
120
	if validationErr != nil {
121
		return validationWarns, validationErr.ToAggregate()
122
	}
123
	crds, err := getAllCRDsForXRD(newObj)
124
	if err != nil {
125
		return warns, xperrors.Wrap(err, "cannot get CRDs for CompositeResourceDefinition")
126
	}
127
	for _, crd := range crds {
128
		// Can't use validation.ValidateCustomResourceDefinition because it leads to dependency errors,
129
		// see https://github.com/kubernetes/apiextensions-apiserver/issues/59
130
		// if errs := validation.ValidateCustomResourceDefinition(ctx, crd); len(errs) != 0 {
131
		//	return warns, errors.Wrap(errs.ToAggregate(), "invalid CRD generated for CompositeResourceDefinition")
132
		//}
133
		//
134
		// We need to be able to handle both cases:
135
		// 1. both CRDs exists already, which should be most of the time
136
		// 2. Claim's CRD does not exist yet, e.g. the user updated the XRD spec
137
		// which previously did not specify a claim.
138
		err := v.dryRunUpdateOrCreateIfNotFound(ctx, crd)
139
		if err != nil {
140
			return warns, v.rewriteError(err, newObj, crd)
141
		}
142
	}
143

144
	return warns, nil
145
}
146

147
func (v *validator) dryRunUpdateOrCreateIfNotFound(ctx context.Context, crd *apiextv1.CustomResourceDefinition) error {
148
	got := crd.DeepCopy()
149
	err := v.client.Get(ctx, client.ObjectKey{Name: crd.Name}, got)
150
	if err == nil {
151
		got.Spec = crd.Spec
152
		return v.client.Update(ctx, got, client.DryRunAll)
153
	}
154
	if kerrors.IsNotFound(err) {
155
		return v.client.Create(ctx, crd, client.DryRunAll)
156
	}
157
	return err
158
}
159

160
// ValidateDelete always allows delete requests.
161
func (v *validator) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
162
	return nil, nil
163
}
164

165
func (v *validator) rewriteError(err error, in *v1.CompositeResourceDefinition, crd *apiextv1.CustomResourceDefinition) error {
166
	// the handler is just discarding wrapping errors unfortunately, so
167
	// we need to unwrap it here, modify its content and return that
168
	// instead
169
	if err == nil {
170
		return nil
171
	}
172
	var apiErr *kerrors.StatusError
173
	if errors.As(err, &apiErr) {
174
		apiErr.ErrStatus.Message = "invalid CRD generated for CompositeResourceDefinition: " + apiErr.ErrStatus.Message
175
		apiErr.ErrStatus.Details.Kind = v1.CompositeResourceDefinitionKind
176
		apiErr.ErrStatus.Details.Group = v1.Group
177
		apiErr.ErrStatus.Details.Name = in.GetName()
178
		for i, cause := range apiErr.ErrStatus.Details.Causes {
179
			cause.Field = fmt.Sprintf("<generated_CRD_%q>.%s", crd.GetName(), cause.Field)
180
			apiErr.ErrStatus.Details.Causes[i] = cause
181
		}
182
		return apiErr
183
	}
184
	return err
185
}
186

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.