Skip to content

Commit

Permalink
Add Support for Scaffolding Webhooks for External Types
Browse files Browse the repository at this point in the history
This update introduces support for scaffolding webhooks for external types, which are APIs/CRDs defined in other projects
  • Loading branch information
camilamacedo86 committed Oct 11, 2024
1 parent e451dfe commit 1be2b8b
Show file tree
Hide file tree
Showing 22 changed files with 743 additions and 6 deletions.
8 changes: 8 additions & 0 deletions docs/book/src/reference/using_an_external_resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ Also, the RBAC role:
This scaffolds a controller for the external type but skips creating new resource
definitions since the type is defined in an external project.

### Creating a Webhook to Manage an External Type

Following an example:

```shell
kubebuilder create webhook --group certmanager --version v1 --kind Issuer --defaulting --programmatic-validation --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 --external-api-domain=cert-manager.io
```

## Managing Core Types

Core Kubernetes API types, such as `Pods`, `Services`, and `Deployments`, are predefined by Kubernetes.
Expand Down
11 changes: 8 additions & 3 deletions pkg/plugins/common/kustomize/v2/scaffolds/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func (s *webhookScaffolder) Scaffold() error {
return fmt.Errorf("error updating resource: %w", err)
}

if err := scaffold.Execute(
buildScaffold := []machinery.Builder{
&kdefault.ManagerWebhookPatch{},
&webhook.Kustomization{Force: s.force},
&webhook.KustomizeConfig{},
Expand All @@ -84,8 +84,13 @@ func (s *webhookScaffolder) Scaffold() error {
&patches.EnableWebhookPatch{},
&patches.EnableCAInjectionPatch{},
&network_policy.NetworkPolicyAllowWebhooks{},
&crd.Kustomization{},
); err != nil {
}

if !s.resource.External {
buildScaffold = append(buildScaffold, &crd.Kustomization{})
}

if err := scaffold.Execute(buildScaffold...); err != nil {
return fmt.Errorf("error scaffolding kustomize webhook manifests: %v", err)
}

Expand Down
32 changes: 29 additions & 3 deletions pkg/plugins/golang/v4/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package v4

import (
"errors"
"fmt"

"github.com/spf13/pflag"
Expand Down Expand Up @@ -82,6 +83,14 @@ func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) {
"[DEPRECATED] Attempts to create resource under the API directory (legacy path). "+
"This option will be removed in future versions.")

fs.StringVar(&p.options.ExternalAPIPath, "external-api-path", "",
"Specify the Go package import path for the external API. This is used to scaffold controllers for resources "+
"defined outside this project (e.g., github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1).")

fs.StringVar(&p.options.ExternalAPIDomain, "external-api-domain", "",
"Specify the domain name for the external API. This domain is used to generate accurate RBAC "+
"markers and permissions for the external resources (e.g., cert-manager.io).")

fs.BoolVar(&p.force, "force", false,
"attempt to create resource even if it already exists")
}
Expand All @@ -94,6 +103,19 @@ func (p *createWebhookSubcommand) InjectConfig(c config.Config) error {
func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error {
p.resource = res

// Ensure that if any external API flag is set, both must be provided.
if len(p.options.ExternalAPIPath) != 0 || len(p.options.ExternalAPIDomain) != 0 {
if len(p.options.ExternalAPIPath) == 0 || len(p.options.ExternalAPIDomain) == 0 {
return errors.New("Both '--external-api-path' and '--external-api-domain' must be " +
"specified together when referencing an external API.")
}
}

if len(p.options.ExternalAPIPath) != 0 && len(p.options.ExternalAPIDomain) != 0 && p.isLegacyPath {
return errors.New("You cannot scaffold webhooks for external types " +
"using the legacy path")
}

p.options.UpdateResource(p.resource, p.config)

if err := p.resource.Validate(); err != nil {
Expand All @@ -106,9 +128,13 @@ func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error {
}

// check if resource exist to create webhook
if r, err := p.config.GetResource(p.resource.GVK); err != nil {
return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName)
} else if r.Webhooks != nil && !r.Webhooks.IsEmpty() && !p.force {
resValue, err := p.config.GetResource(p.resource.GVK)
res = &resValue
if err != nil {
if !p.resource.External {
return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName)
}
} else if res.Webhooks != nil && !res.Webhooks.IsEmpty() && !p.force {
return fmt.Errorf("webhook resource already exists")
}

Expand Down
4 changes: 4 additions & 0 deletions test/testdata/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ function scaffold_test_project {
$kb create webhook --group crew --version v1 --kind Admiral --plural=admirales --defaulting
# Controller for External types
$kb create api --group certmanager --version v1 --kind Certificate --controller=true --resource=false --make=false --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 --external-api-domain=cert-manager.io
# Webhook for External types
$kb create webhook --group certmanager --version v1 --kind Issuer --defaulting --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 --external-api-domain=cert-manager.io
fi

if [[ $project =~ multigroup ]]; then
Expand All @@ -73,6 +75,8 @@ function scaffold_test_project {
$kb create api --group fiz --version v1 --kind Bar --controller=true --resource=true --make=false
# Controller for External types
$kb create api --group certmanager --version v1 --kind Certificate --controller=true --resource=false --make=false --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 --external-api-domain=cert-manager.io
# Webhook for External types
$kb create webhook --group certmanager --version v1 --kind Issuer --defaulting --programmatic-validation --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 --external-api-domain=cert-manager.io
fi

if [[ $project =~ multigroup ]] || [[ $project =~ with-plugins ]] ; then
Expand Down
10 changes: 10 additions & 0 deletions testdata/project-v4-multigroup/PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,16 @@ resources:
kind: Certificate
path: github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1
version: v1
- domain: cert-manager.io
external: true
group: certmanager
kind: Issuer
path: github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1
version: v1
webhooks:
defaulting: true
validation: true
webhookVersion: v1
- api:
crdVersion: v1
namespaced: true
Expand Down
8 changes: 8 additions & 0 deletions testdata/project-v4-multigroup/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import (
foopolicycontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/foo.policy"
seacreaturescontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/sea-creatures"
shipcontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/ship"
webhookcertmanagerv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/certmanager/v1"
webhookcrewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/crew/v1"
webhookexamplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1"
webhookshipv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/ship/v1"
Expand Down Expand Up @@ -283,6 +284,13 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "Certificate")
os.Exit(1)
}
// nolint:goconst
if os.Getenv("ENABLE_WEBHOOKS") != "false" {
if err = webhookcertmanagerv1.SetupIssuerWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "Issuer")
os.Exit(1)
}
}
if err = (&examplecomcontroller.MemcachedReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# The following patch adds a directive for certmanager to inject CA into the CRD
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME
name: issuers.certmanager.cert-manager.io
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# The following patch enables a conversion webhook for the CRD
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: issuers.certmanager.cert-manager.io
spec:
conversion:
strategy: Webhook
webhook:
clientConfig:
service:
namespace: system
name: webhook-service
path: /convert
conversionReviewVersions:
- v1
40 changes: 40 additions & 0 deletions testdata/project-v4-multigroup/config/webhook/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ kind: MutatingWebhookConfiguration
metadata:
name: mutating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
service:
name: webhook-service
namespace: system
path: /mutate-certmanager-cert-manager-io-v1-issuer
failurePolicy: Fail
name: missuer-v1.kb.io
rules:
- apiGroups:
- certmanager.cert-manager.io
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- issuers
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
Expand Down Expand Up @@ -50,6 +70,26 @@ kind: ValidatingWebhookConfiguration
metadata:
name: validating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
service:
name: webhook-service
namespace: system
path: /validate-certmanager-cert-manager-io-v1-issuer
failurePolicy: Fail
name: vissuer-v1.kb.io
rules:
- apiGroups:
- certmanager.cert-manager.io
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- issuers
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
Expand Down
40 changes: 40 additions & 0 deletions testdata/project-v4-multigroup/dist/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1814,6 +1814,26 @@ kind: MutatingWebhookConfiguration
metadata:
name: project-v4-multigroup-mutating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
service:
name: project-v4-multigroup-webhook-service
namespace: project-v4-multigroup-system
path: /mutate-certmanager-cert-manager-io-v1-issuer
failurePolicy: Fail
name: missuer-v1.kb.io
rules:
- apiGroups:
- certmanager.cert-manager.io
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- issuers
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
Expand Down Expand Up @@ -1860,6 +1880,26 @@ kind: ValidatingWebhookConfiguration
metadata:
name: project-v4-multigroup-validating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
service:
name: project-v4-multigroup-webhook-service
namespace: project-v4-multigroup-system
path: /validate-certmanager-cert-manager-io-v1-issuer
failurePolicy: Fail
name: vissuer-v1.kb.io
rules:
- apiGroups:
- certmanager.cert-manager.io
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- issuers
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
Expand Down
Loading

0 comments on commit 1be2b8b

Please sign in to comment.