Skip to content

support gen-manifests and disable-installer #158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 39 additions & 22 deletions example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,40 +39,51 @@ import (
var log = logf.Log.WithName("example-controller")

func main() {
var genManifests, disableInstaller bool
flag.BoolVar(&genManifests, "generate-webhook-manifests", false,
"generate manifests for webhook related resources")
flag.BoolVar(&disableInstaller, "disable-installer", false,
"disable the installer in the webhook server, so it won't install webhook related resources during bootstrapping")

flag.Parse()
logf.SetLogger(logf.ZapLogger(false))
entryLog := log.WithName("entrypoint")

// Setup a Manager
entryLog.Info("setting up manager")
mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{})
if err != nil {
entryLog.Error(err, "unable to set up overall controller manager")
os.Exit(1)
}

// Setup a new controller to Reconciler ReplicaSets
c, err := controller.New("foo-controller", mgr, controller.Options{
Reconciler: &reconcileReplicaSet{client: mgr.GetClient(), log: log.WithName("reconciler")},
})
if err != nil {
entryLog.Error(err, "unable to set up individual controller")
os.Exit(1)
}

// Watch ReplicaSets and enqueue ReplicaSet object key
if err := c.Watch(&source.Kind{Type: &appsv1.ReplicaSet{}}, &handler.EnqueueRequestForObject{}); err != nil {
entryLog.Error(err, "unable to watch ReplicaSets")
os.Exit(1)
}

// Watch Pods and enqueue owning ReplicaSet key
if err := c.Watch(&source.Kind{Type: &corev1.Pod{}},
&handler.EnqueueRequestForOwner{OwnerType: &appsv1.ReplicaSet{}, IsController: true}); err != nil {
entryLog.Error(err, "unable to watch Pods")
os.Exit(1)
if !genManifests {
// Setup a new controller to Reconciler ReplicaSets
entryLog.Info("Setting up controller")
c, err := controller.New("foo-controller", mgr, controller.Options{
Reconciler: &reconcileReplicaSet{client: mgr.GetClient(), log: log.WithName("reconciler")},
})
if err != nil {
entryLog.Error(err, "unable to set up individual controller")
os.Exit(1)
}

// Watch ReplicaSets and enqueue ReplicaSet object key
if err := c.Watch(&source.Kind{Type: &appsv1.ReplicaSet{}}, &handler.EnqueueRequestForObject{}); err != nil {
entryLog.Error(err, "unable to watch ReplicaSets")
os.Exit(1)
}

// Watch Pods and enqueue owning ReplicaSet key
if err := c.Watch(&source.Kind{Type: &corev1.Pod{}},
&handler.EnqueueRequestForOwner{OwnerType: &appsv1.ReplicaSet{}, IsController: true}); err != nil {
entryLog.Error(err, "unable to watch Pods")
os.Exit(1)
}
}

// Setup webhooks
entryLog.Info("setting up webhooks")
mutatingWebhook, err := builder.NewWebhookBuilder().
Name("mutating.k8s.io").
Mutating().
Expand All @@ -99,9 +110,12 @@ func main() {
os.Exit(1)
}

entryLog.Info("setting up webhook server")
as, err := webhook.NewServer("foo-admission-server", mgr, webhook.ServerOptions{
Port: 9876,
CertDir: "/tmp/cert",
Port: 9876,
CertDir: "/tmp/cert",
GenerateManifests: genManifests,
DisableInstaller: disableInstaller,
BootstrapOptions: &webhook.BootstrapOptions{
Secret: &apitypes.NamespacedName{
Namespace: "default",
Expand All @@ -122,12 +136,15 @@ func main() {
entryLog.Error(err, "unable to create a new webhook server")
os.Exit(1)
}

entryLog.Info("registering webhooks to the webhook server")
err = as.Register(mutatingWebhook, validatingWebhook)
if err != nil {
entryLog.Error(err, "unable to register webhooks in the admission server")
os.Exit(1)
}

entryLog.Info("starting manager")
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
entryLog.Error(err, "unable to run manager")
os.Exit(1)
Expand Down
149 changes: 119 additions & 30 deletions pkg/webhook/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ import (
"path"
"strconv"

"github.com/ghodss/yaml"

"k8s.io/api/admissionregistration/v1beta1"
admissionregistration "k8s.io/api/admissionregistration/v1beta1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -93,6 +92,15 @@ func (s *Server) setBootstrappingDefault() {
s.Host = &varString
}

if s.Writer == nil {
s.Writer = os.Stdout
}
if s.GenerateManifests {
s.Client = &writerClient{
writer: s.Writer,
}
}

var certWriter writer.CertWriter
var err error
if s.Secret != nil {
Expand All @@ -114,12 +122,10 @@ func (s *Server) setBootstrappingDefault() {
s.certProvisioner = &cert.Provisioner{
CertWriter: certWriter,
}
if s.Writer == nil {
s.Writer = os.Stdout
}
}

// installWebhookConfig writes the configuration of admissionWebhookConfiguration in yaml format if dryrun is true.
// installWebhookConfig writes the configuration of admissionWebhookConfiguration in yaml format
// if GenerateManifests is true.
// Otherwise, it creates the the admissionWebhookConfiguration objects and service if any.
// It also provisions the certificate for the admission server.
func (s *Server) installWebhookConfig() error {
Expand All @@ -129,6 +135,14 @@ func (s *Server) installWebhookConfig() error {
return s.err
}

if !s.GenerateManifests && s.DisableInstaller {
log.Info("generating webhook manifests is disabled and webhook installer is also disabled")
return nil
}
if s.GenerateManifests {
log.Info("generating webhook related manifests")
}

var err error
s.webhookConfigurations, err = s.whConfigs()
if err != nil {
Expand All @@ -145,40 +159,18 @@ func (s *Server) installWebhookConfig() error {
_, err = s.certProvisioner.Provision(cert.Options{
ClientConfig: cc,
Objects: s.webhookConfigurations,
Dryrun: s.Dryrun,
})
if err != nil {
return err
}

if s.Dryrun {
// TODO: print here
// if dryrun, return the AdmissionWebhookConfiguration in yaml format.
return s.genYamlConfig(objects)
if s.GenerateManifests {
objects = append(objects, s.deployment())
}

return batchCreateOrReplace(s.Client, objects...)
}

// genYamlConfig generates yaml config for admissionWebhookConfiguration
func (s *Server) genYamlConfig(objs []runtime.Object) error {
for _, obj := range objs {
_, err := s.Writer.Write([]byte("---"))
if err != nil {
return err
}
b, err := yaml.Marshal(obj)
if err != nil {
return err
}
_, err = s.Writer.Write(b)
if err != nil {
return err
}
}
return nil
}

func (s *Server) getClientConfig() (*admissionregistration.WebhookClientConfig, error) {
if s.Host != nil && s.Service != nil {
return nil, errors.New("URL and Service can't be set at the same time")
Expand Down Expand Up @@ -360,3 +352,100 @@ func (s *Server) service() runtime.Object {
}
return svc
}

func (s *Server) deployment() runtime.Object {
namespace := "default"
if s.Secret != nil {
namespace = s.Secret.Namespace
}

labels := map[string]string{
"app": "webhook-server",
}
if s.Service != nil {
for k, v := range s.Service.Selectors {
labels[k] = v
}
}

image := "webhook-server:latest"
if len(os.Getenv("IMG")) != 0 {
image = os.Getenv("IMG")
}

readOnly := false
if s.Service != nil {
readOnly = true
}

var volumeSource corev1.VolumeSource
if s.Secret != nil {
var mode int32 = 420
volumeSource = corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
DefaultMode: &mode,
SecretName: s.Secret.Name,
},
}
} else {
volumeSource = corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
}
}

return &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Name: "webhook-server",
Namespace: namespace,
Labels: labels,
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: labels,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "webhook-server-container",
Image: image,
ImagePullPolicy: corev1.PullAlways,
Command: []string{
"/bin/sh",
"-c",
"exec ./manager --disable-installer",
},
Ports: []corev1.ContainerPort{
{
Name: "webhook-server",
Protocol: corev1.ProtocolTCP,
ContainerPort: s.Port,
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "cert",
MountPath: s.CertDir,
ReadOnly: readOnly,
},
},
},
},
Volumes: []corev1.Volume{
{
Name: "cert",
VolumeSource: volumeSource,
},
},
},
},
},
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about just printing out the webhook config only. Outputting other manifests means, now we have two different ways to generate the manifests for controller ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO using generated Deployment (can also be a StatefulSet with a headless Service) is less error-prone.
There are quite some places need to be set correctly in order to make the webhook server binary work correctly.
We can discuss in person tomorrow :)

4 changes: 4 additions & 0 deletions pkg/webhook/internal/cert/generator/certgenerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ type Artifacts struct {
Key []byte
// PEM encoded serving certificate
Cert []byte
// PEM encoded CA private key
CAKey []byte
// PEM encoded CA certificate
CACert []byte
}
Expand All @@ -31,4 +33,6 @@ type Artifacts struct {
type CertGenerator interface {
// Generate returns a Artifacts struct.
Generate(CommonName string) (*Artifacts, error)
// SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert.
SetCA(caKey, caCert []byte)
}
14 changes: 14 additions & 0 deletions pkg/webhook/internal/cert/generator/fake/certgenerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,37 @@ limitations under the License.
package fake

import (
"bytes"
"fmt"

"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator"
)

// CertGenerator is a certGenerator for testing.
type CertGenerator struct {
CAKey []byte
CACert []byte
DNSNameToCertArtifacts map[string]*generator.Artifacts
}

var _ generator.CertGenerator = &CertGenerator{}

// SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert.
func (cp *CertGenerator) SetCA(CAKey, CACert []byte) {
cp.CAKey = CAKey
cp.CACert = CACert
}

// Generate generates certificates by matching a common name.
func (cp *CertGenerator) Generate(commonName string) (*generator.Artifacts, error) {
certs, found := cp.DNSNameToCertArtifacts[commonName]
if !found {
return nil, fmt.Errorf("failed to find common name %q in the certGenerator", commonName)
}
if cp.CAKey != nil && cp.CACert != nil &&
!bytes.Contains(cp.CAKey, []byte("invalid")) && !bytes.Contains(cp.CACert, []byte("invalid")) {
certs.CAKey = cp.CAKey
certs.CACert = cp.CACert
}
return certs, nil
}
Loading