Skip to content

Commit

Permalink
cert-manager: don't re-install if all resources already exist
Browse files Browse the repository at this point in the history
  • Loading branch information
munnerz committed Jul 9, 2020
1 parent 4336293 commit 465adf5
Showing 1 changed file with 102 additions and 44 deletions.
146 changes: 102 additions & 44 deletions cmd/clusterctl/client/cluster/cert_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ limitations under the License.
package cluster

import (
"context"
"time"

"github.com/go-logr/logr"
"github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/client"

clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
Expand All @@ -45,8 +48,8 @@ const (

// CertManagerClient has methods to work with cert-manager components in the cluster.
type CertManagerClient interface {
// EnsureCertManager makes sure the cert-manager Webhook is Available in a cluster:
// this is a requirement to install a new provider
// EnsureCertManager makes sure cert-manager is running and its API is available.
// This is required to install a new provider.
EnsureCertManager() error

// Images return the list of images required for installing the cert-manager.
Expand All @@ -73,6 +76,7 @@ func newCertMangerClient(configClient config.Client, proxy Proxy, pollImmediateW
}

// Images return the list of images required for installing the cert-manager.
// TODO: should this method return an empty slice if cert-manager is already installed?
func (cm *certManagerClient) Images() ([]string, error) {
// Gets the cert-manager objects from the embedded assets.
objs, err := cm.getManifestObjs()
Expand All @@ -87,23 +91,34 @@ func (cm *certManagerClient) Images() ([]string, error) {
return images, nil
}

// EnsureCertManager makes sure the cert-manager Web-hook is Available in a cluster:
// this is a requirement to install a new provider
// EnsureCertManager makes sure cert-manager is running and its API is available.
// This is required to install a new provider.
// Nb. In order to provide a simpler out-of-the box experience, the cert-manager manifest
// is embedded in the clusterctl binary.
func (cm *certManagerClient) EnsureCertManager() error {
log := logf.Log

// Otherwise install cert-manager
log.Info("Installing cert-manager")

// Gets the cert-manager objects from the embedded assets.
objs, err := cm.getManifestObjs()
if err != nil {
return err
}

// installs the web-hook
// Skip re-installing cert-manager if all objects already exist
allObjectsExist, err := cm.allObjectsExist(ctx, log, objs)
if err != nil {
return err
}
// Perform a rough check to see if all cert-manager resources exist.
if allObjectsExist {
log.Info("Skipping installing cert-manager as it is already installed")
// Wait for the cert-manager API to be ready, in case it is not already.
return cm.waitForAPIReady(ctx, log)
}

log.Info("Installing cert-manager")

// Install all cert-manager manifests
createCertManagerBackoff := newWriteBackoff()
objs = utilresource.SortForCreate(objs)
for i := range objs {
Expand All @@ -119,45 +134,11 @@ func (cm *certManagerClient) EnsureCertManager() error {
}
}

// Waits for for the cert-manager web-hook to be available.
log.Info("Waiting for cert-manager to be available...")

testObjs, err := getTestResourcesManifestObjs()
if err != nil {
// Wait for the cert-manager API to be ready to accept requests
if err := cm.waitForAPIReady(ctx, log); err != nil {
return err
}

for i := range testObjs {
o := testObjs[i]
log.V(5).Info("Creating", logf.UnstructuredToValues(o)...)

// Create the Kubernetes object.
// This is wrapped with a retry as the cert-manager API may not be available
// yet, so we need to keep retrying until it is.
if err := cm.pollImmediateWaiter(waitCertManagerInterval, cm.getWaitTimeout(), func() (bool, error) {
if err := cm.createObj(o); err != nil {
log.V(5).Info("Failed to create cert-manager test resource", logf.UnstructuredToValues(o)...)
return false, nil
}
return true, nil
}); err != nil {
return err
}
}
for i := range testObjs {
o := testObjs[i]
log.V(5).Info("Deleting", logf.UnstructuredToValues(o)...)

cl, err := cm.proxy.NewClient()
if err != nil {
return err
}

if err := cl.Delete(ctx, &o); err != nil {
return err
}
}

return nil
}

Expand Down Expand Up @@ -236,3 +217,80 @@ func (cm *certManagerClient) createObj(o unstructured.Unstructured) error {
}
return nil
}

// allObjectsExist checks if all the objects with the given name already exist
func (cm *certManagerClient) allObjectsExist(ctx context.Context, log logr.Logger, objs []unstructured.Unstructured) (bool, error) {
c, err := cm.proxy.NewClient()
if err != nil {
return false, err
}

for _, obj := range objs {
log.V(5).Info("Checking", logf.UnstructuredToValues(obj)...)
key, err := client.ObjectKeyFromObject(&obj)
if err != nil {
return false, err
}


// create a DeepCopy of the object so we can perform the Get into it
// without modifying the stored value
newObj := obj.DeepCopyObject()
if err := c.Get(ctx, key, newObj); err != nil {
if apierrors.IsNotFound(err) {
return false, nil
}

return false, err
}
}

return true, nil
}

// waitForAPIReady will attempt to create the cert-manager 'test assets' (i.e. a basic
// Issuer and Certificate).
// This ensures that the Kubernetes apiserver is ready to serve resources within the
// cert-manager API group.
func (cm *certManagerClient) waitForAPIReady(ctx context.Context, log logr.Logger) error {
// Waits for for the cert-manager web-hook to be available.
log.Info("Waiting for cert-manager to be available...")

testObjs, err := getTestResourcesManifestObjs()
if err != nil {
return err
}

for i := range testObjs {
o := testObjs[i]
log.V(5).Info("Creating", logf.UnstructuredToValues(o)...)

// Create the Kubernetes object.
// This is wrapped with a retry as the cert-manager API may not be available
// yet, so we need to keep retrying until it is.
if err := cm.pollImmediateWaiter(waitCertManagerInterval, cm.getWaitTimeout(), func() (bool, error) {
if err := cm.createObj(o); err != nil {
log.V(5).Info("Failed to create cert-manager test resource", logf.UnstructuredToValues(o)...)
return false, nil
}
return true, nil
}); err != nil {
return err
}
}
for i := range testObjs {
o := testObjs[i]
log.V(5).Info("Deleting", logf.UnstructuredToValues(o)...)

cl, err := cm.proxy.NewClient()
if err != nil {
return err
}

if err := cl.Delete(ctx, &o); err != nil {
return err
}
}

return nil
}

0 comments on commit 465adf5

Please sign in to comment.