github.com/docker/compose-on-kubernetes@v0.5.0/install/uninstall.go (about)

     1  package install
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	stacksclient "github.com/docker/compose-on-kubernetes/api/client/clientset/typed/compose/v1beta1"
     8  	log "github.com/sirupsen/logrus"
     9  	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    10  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    11  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  	appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
    13  	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    14  	rbacv1 "k8s.io/client-go/kubernetes/typed/rbac/v1"
    15  	"k8s.io/client-go/rest"
    16  	typedkubeaggreagatorv1beta1 "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1"
    17  )
    18  
    19  var (
    20  	deletePolicy           = metav1.DeletePropagationForeground
    21  	deleteBackgroundPolicy = metav1.DeletePropagationBackground
    22  	deleteOrphanPolicy     = metav1.DeletePropagationOrphan
    23  	deleteOptions          = metav1.DeleteOptions{
    24  		PropagationPolicy: &deletePolicy,
    25  	}
    26  	deleteBackgroundOptions = metav1.DeleteOptions{
    27  		PropagationPolicy: &deleteBackgroundPolicy,
    28  	}
    29  	listOptions = metav1.ListOptions{
    30  		LabelSelector: everythingSelector,
    31  	}
    32  )
    33  
    34  func uninstallErrorFilter(err error) error {
    35  	if apierrors.IsNotFound(err) {
    36  		return nil
    37  	}
    38  	return err
    39  }
    40  
    41  func uninstallCore(config *rest.Config, namespace string) error {
    42  	client, err := corev1.NewForConfig(config)
    43  	if err != nil {
    44  		return err
    45  	}
    46  	if err = uninstallErrorFilter(client.Secrets(namespace).DeleteCollection(&deleteOptions, listOptions)); err != nil {
    47  		return err
    48  	}
    49  	svcs, err := client.Services(namespace).List(listOptions)
    50  	if uninstallErrorFilter(err) != nil {
    51  		return err
    52  	}
    53  	for _, svc := range svcs.Items {
    54  		if err = uninstallErrorFilter(client.Services(namespace).Delete(svc.Name, &deleteBackgroundOptions)); err != nil {
    55  			return err
    56  		}
    57  	}
    58  	return uninstallErrorFilter(client.ServiceAccounts(namespace).DeleteCollection(&deleteOptions, listOptions))
    59  }
    60  
    61  func uninstallApps(config *rest.Config, namespace string) error {
    62  	apps, err := appsv1.NewForConfig(config)
    63  	if uninstallErrorFilter(err) != nil {
    64  		return err
    65  	}
    66  	return uninstallErrorFilter(apps.Deployments(namespace).DeleteCollection(&deleteOptions, listOptions))
    67  }
    68  
    69  func uninstallRbac(config *rest.Config) error {
    70  	rbac, err := rbacv1.NewForConfig(config)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	if err = uninstallErrorFilter(rbac.ClusterRoleBindings().DeleteCollection(&deleteOptions, listOptions)); err != nil {
    75  		return err
    76  	}
    77  	if err = uninstallErrorFilter(rbac.ClusterRoles().DeleteCollection(&deleteOptions, listOptions)); err != nil {
    78  		return err
    79  	}
    80  	return uninstallErrorFilter(rbac.RoleBindings("kube-system").DeleteCollection(&deleteOptions, listOptions))
    81  }
    82  
    83  func orphanAllStacks(stacks stacksclient.ComposeV1beta1Interface) error {
    84  
    85  	type namespaceAndName struct {
    86  		namespace string
    87  		name      string
    88  	}
    89  	toDelete := []namespaceAndName{}
    90  	listOpts := metav1.ListOptions{}
    91  	for {
    92  		stackList, err := stacks.Stacks(metav1.NamespaceAll).List(listOpts)
    93  		if err != nil {
    94  			return err
    95  		}
    96  		for _, stack := range stackList.Items {
    97  			toDelete = append(toDelete, namespaceAndName{namespace: stack.Namespace, name: stack.Name})
    98  		}
    99  		if stackList.Continue == "" {
   100  			break
   101  		}
   102  		listOpts.Continue = stackList.Continue
   103  	}
   104  
   105  	log.Infof("Orphaning %d stack(s)", len(toDelete))
   106  	for _, stack := range toDelete {
   107  		if err := stacks.Stacks(stack.namespace).Delete(stack.name, &metav1.DeleteOptions{
   108  			PropagationPolicy: &deleteOrphanPolicy,
   109  		}); err != nil {
   110  			return err
   111  		}
   112  	}
   113  	// wait for all stacks to be effectively deleted
   114  	log.Info("Waiting for stack to be removed")
   115  	for {
   116  		list, err := stacks.Stacks(metav1.NamespaceAll).List(metav1.ListOptions{})
   117  		if err != nil {
   118  			return err
   119  		}
   120  		if len(list.Items) == 0 {
   121  			break
   122  		}
   123  		log.Infof("%d stack(s) waiting to be removed", len(list.Items))
   124  		time.Sleep(time.Second)
   125  	}
   126  	return nil
   127  }
   128  
   129  // UninstallCRD uninstalls the CustomResourceDefinition and preserves running stacks
   130  func UninstallCRD(config *rest.Config) error {
   131  	crds, err := apiextensionsclient.NewForConfig(config)
   132  	if err != nil {
   133  		return err
   134  	}
   135  	_, err = crds.ApiextensionsV1beta1().CustomResourceDefinitions().Get("stacks.compose.docker.com", metav1.GetOptions{})
   136  	if err != nil {
   137  		if apierrors.IsNotFound(err) {
   138  			return nil
   139  		}
   140  		return err
   141  	}
   142  
   143  	// deleteOrphanPolicy is not propagated if we straight out delete the CRD,
   144  	// so let's go and delete the stacks manually before
   145  	stacks, err := stacksclient.NewForConfig(config)
   146  	if err != nil {
   147  		return err
   148  	}
   149  	if err = orphanAllStacks(stacks); err != nil {
   150  		return err
   151  	}
   152  
   153  	log.Info("Removing CRD")
   154  	if err = uninstallErrorFilter(crds.ApiextensionsV1beta1().CustomResourceDefinitions().Delete("stacks.compose.docker.com",
   155  		&metav1.DeleteOptions{})); err != nil {
   156  		return err
   157  	}
   158  	// wait for crd removal
   159  	for {
   160  		_, err = crds.ApiextensionsV1beta1().CustomResourceDefinitions().Get("stacks.compose.docker.com", metav1.GetOptions{})
   161  		if err != nil {
   162  			if apierrors.IsNotFound(err) {
   163  				return nil
   164  			}
   165  			return err
   166  		}
   167  		time.Sleep(time.Second)
   168  	}
   169  }
   170  
   171  // Uninstall uninstalls the Compose feature.
   172  func Uninstall(config *rest.Config, namespace string, keepCRD bool) error {
   173  	log.Debugf("Uninstall from namespace %q", namespace)
   174  
   175  	crds, err := apiextensionsclient.NewForConfig(config)
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	err = uninstallApps(config, namespace)
   181  	if err != nil {
   182  		return err
   183  	}
   184  
   185  	if !keepCRD {
   186  		err = crds.ApiextensionsV1beta1().CustomResourceDefinitions().Delete("stacks.compose.docker.com", &deleteOptions)
   187  		if err != nil && !apierrors.IsNotFound(err) {
   188  			return err
   189  		}
   190  	}
   191  	err = uninstallRbac(config)
   192  	if err != nil {
   193  		return err
   194  	}
   195  
   196  	aggregator, err := typedkubeaggreagatorv1beta1.NewForConfig(config)
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	apisvcs, err := aggregator.APIServices().List(metav1.ListOptions{})
   202  	if err != nil {
   203  		return err
   204  	}
   205  	for _, apisvc := range apisvcs.Items {
   206  		if apisvc.Labels[fryKey] == composeAPIServerFry {
   207  			err = aggregator.APIServices().Delete(apisvc.Name, &deleteBackgroundOptions)
   208  			if err != nil {
   209  				return err
   210  			}
   211  		}
   212  	}
   213  	return uninstallCore(config, namespace)
   214  }
   215  func isDone(ctx context.Context) bool {
   216  	select {
   217  	case <-ctx.Done():
   218  		return true
   219  	default:
   220  		return false
   221  	}
   222  }
   223  
   224  func waitForNoCrd(ctx context.Context, config *rest.Config, namespace string) error {
   225  	crds, err := apiextensionsclient.NewForConfig(config)
   226  	if err != nil {
   227  		return err
   228  	}
   229  	for {
   230  		if isDone(ctx) {
   231  			return context.DeadlineExceeded
   232  		}
   233  		lst, err := crds.ApiextensionsV1beta1().CustomResourceDefinitions().List(listOptions)
   234  		if err != nil {
   235  			return err
   236  		}
   237  		if len(lst.Items) == 0 {
   238  			return nil
   239  		}
   240  		time.Sleep(time.Second)
   241  	}
   242  }
   243  
   244  func waitForNoApps(ctx context.Context, config *rest.Config, namespace string) error {
   245  	apps, err := appsv1.NewForConfig(config)
   246  	if err != nil {
   247  		return err
   248  	}
   249  	for {
   250  		if isDone(ctx) {
   251  			return context.DeadlineExceeded
   252  		}
   253  		lst, err := apps.Deployments(namespace).List(listOptions)
   254  		if err != nil {
   255  			return err
   256  		}
   257  		if len(lst.Items) == 0 {
   258  			return nil
   259  		}
   260  		time.Sleep(time.Second)
   261  	}
   262  }
   263  
   264  func waitForNoRbac(ctx context.Context, config *rest.Config, namespace string) error {
   265  	rbac, err := rbacv1.NewForConfig(config)
   266  	if err != nil {
   267  		return err
   268  	}
   269  
   270  	for {
   271  		if isDone(ctx) {
   272  			return context.DeadlineExceeded
   273  		}
   274  		lst1, err := rbac.ClusterRoleBindings().List(listOptions)
   275  		// if rbac is not enabled, List will fail with status 404 instead of returning an empty list
   276  		// in that case we can just leave early
   277  		if apierrors.IsNotFound(err) {
   278  			return nil
   279  		}
   280  		if err != nil {
   281  			return err
   282  		}
   283  		if len(lst1.Items) > 0 {
   284  			time.Sleep(time.Second)
   285  			continue
   286  		}
   287  		lst2, err := rbac.ClusterRoles().List(listOptions)
   288  		if err != nil {
   289  			return err
   290  		}
   291  		if len(lst2.Items) > 0 {
   292  			time.Sleep(time.Second)
   293  			continue
   294  		}
   295  		lst3, err := rbac.RoleBindings("kube-system").List(listOptions)
   296  		if err != nil {
   297  			return err
   298  		}
   299  		if len(lst3.Items) > 0 {
   300  			time.Sleep(time.Second)
   301  			continue
   302  		}
   303  		return nil
   304  	}
   305  }
   306  
   307  func waitForNoAPIAggregation(ctx context.Context, config *rest.Config, namespace string) error {
   308  	aggregator, err := typedkubeaggreagatorv1beta1.NewForConfig(config)
   309  	if err != nil {
   310  		return err
   311  	}
   312  
   313  	for {
   314  		if isDone(ctx) {
   315  			return context.DeadlineExceeded
   316  		}
   317  		apisvcs, err := aggregator.APIServices().List(metav1.ListOptions{})
   318  		if err != nil {
   319  			return err
   320  		}
   321  		for _, apisvc := range apisvcs.Items {
   322  			if apisvc.Labels[fryKey] == composeAPIServerFry {
   323  				time.Sleep(time.Second)
   324  				continue
   325  			}
   326  		}
   327  		return nil
   328  	}
   329  }
   330  func waitForNoCoreComponents(ctx context.Context, config *rest.Config, namespace string) error {
   331  	client, err := corev1.NewForConfig(config)
   332  	if err != nil {
   333  		return err
   334  	}
   335  	for {
   336  		if isDone(ctx) {
   337  			return context.DeadlineExceeded
   338  		}
   339  		secrets, err := client.Secrets(namespace).List(listOptions)
   340  		if err != nil {
   341  			return err
   342  		}
   343  		if len(secrets.Items) > 0 {
   344  			time.Sleep(time.Second)
   345  			continue
   346  		}
   347  
   348  		svcs, err := client.Services(namespace).List(listOptions)
   349  		if err != nil {
   350  			return err
   351  		}
   352  		if len(svcs.Items) > 0 {
   353  			time.Sleep(time.Second)
   354  			continue
   355  		}
   356  
   357  		sas, err := client.ServiceAccounts(namespace).List(listOptions)
   358  		if err != nil {
   359  			return err
   360  		}
   361  		if len(sas.Items) > 0 {
   362  			time.Sleep(time.Second)
   363  			continue
   364  		}
   365  		return nil
   366  	}
   367  }
   368  
   369  type uninstallWaiter func(context.Context, *rest.Config, string) error
   370  
   371  // WaitForUninstallCompletion waits for an unistall operation to complete
   372  func WaitForUninstallCompletion(ctx context.Context, config *rest.Config, namespace string, skipCRD bool) error {
   373  	waiters := []uninstallWaiter{
   374  		waitForNoApps,
   375  		waitForNoRbac,
   376  		waitForNoAPIAggregation,
   377  		waitForNoCoreComponents,
   378  	}
   379  	if !skipCRD {
   380  		waiters = append(waiters, waitForNoCrd)
   381  	}
   382  	for _, w := range waiters {
   383  		if err := w(ctx, config, namespace); err != nil {
   384  			return err
   385  		}
   386  	}
   387  	return nil
   388  }