...

Source file src/github.com/redhat-developer/odo/pkg/kclient/deployments.go

Documentation: github.com/redhat-developer/odo/pkg/kclient

     1  package kclient
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  
     8  	appsv1 "k8s.io/api/apps/v1"
     9  	corev1 "k8s.io/api/core/v1"
    10  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    11  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  	"k8s.io/apimachinery/pkg/fields"
    13  	"k8s.io/apimachinery/pkg/runtime/schema"
    14  	"k8s.io/apimachinery/pkg/types"
    15  	"k8s.io/apimachinery/pkg/watch"
    16  	"k8s.io/klog"
    17  
    18  	odolabels "github.com/redhat-developer/odo/pkg/labels"
    19  )
    20  
    21  func Bool(b bool) *bool {
    22  	return &b
    23  }
    24  
    25  const (
    26  	DeploymentKind       = "Deployment"
    27  	DeploymentAPIVersion = "apps/v1"
    28  )
    29  
    30  // GetDeploymentByName gets a deployment by querying by name
    31  func (c *Client) GetDeploymentByName(name string) (*appsv1.Deployment, error) {
    32  	deployment, err := c.KubeClient.AppsV1().Deployments(c.Namespace).Get(context.TODO(), name, metav1.GetOptions{})
    33  	// TODO(pvala): Figure out why Kind and APIVersion are not added to the deployment object
    34  	deployment.APIVersion = DeploymentAPIVersion
    35  	deployment.Kind = DeploymentKind
    36  	return deployment, err
    37  }
    38  
    39  // GetOneDeployment returns the Deployment object associated with the given component and app
    40  func (c *Client) GetOneDeployment(componentName, appName string, isPartOfComponent bool) (*appsv1.Deployment, error) {
    41  	selector := odolabels.GetSelector(componentName, appName, odolabels.ComponentDevMode, isPartOfComponent)
    42  	return c.GetOneDeploymentFromSelector(selector)
    43  }
    44  
    45  // GetOneDeploymentFromSelector returns the Deployment object associated
    46  // with the given selector.
    47  // An error is thrown when exactly one Deployment is not found for the
    48  // selector.
    49  func (c *Client) GetOneDeploymentFromSelector(selector string) (*appsv1.Deployment, error) {
    50  	deployments, err := c.GetDeploymentFromSelector(selector)
    51  	if err != nil {
    52  		return nil, fmt.Errorf("unable to get Deployments for the selector: %v: %w", selector, err)
    53  	}
    54  
    55  	num := len(deployments)
    56  	if num == 0 {
    57  		return nil, &DeploymentNotFoundError{Selector: selector}
    58  	} else if num > 1 {
    59  		return nil, fmt.Errorf("multiple Deployments exist for the selector: %v. Only one must be present", selector)
    60  	}
    61  
    62  	return &deployments[0], nil
    63  }
    64  
    65  // GetDeploymentFromSelector returns an array of Deployment resources which match the given selector
    66  func (c *Client) GetDeploymentFromSelector(selector string) ([]appsv1.Deployment, error) {
    67  	var deploymentList *appsv1.DeploymentList
    68  	var err error
    69  
    70  	if selector != "" {
    71  		deploymentList, err = c.KubeClient.AppsV1().Deployments(c.Namespace).List(context.TODO(), metav1.ListOptions{
    72  			LabelSelector: selector,
    73  		})
    74  	} else {
    75  		deploymentList, err = c.KubeClient.AppsV1().Deployments(c.Namespace).List(context.TODO(), metav1.ListOptions{
    76  			FieldSelector: fields.Set{"metadata.namespace": c.Namespace}.AsSelector().String(),
    77  		})
    78  	}
    79  	if err != nil {
    80  		return nil, fmt.Errorf("unable to list Deployments: %w", err)
    81  	}
    82  	return deploymentList.Items, nil
    83  }
    84  
    85  func resourceAsJson(resource interface{}) string {
    86  	data, _ := json.MarshalIndent(resource, " ", " ")
    87  	return string(data)
    88  }
    89  
    90  // CreateDeployment creates a deployment based on the given deployment spec
    91  func (c *Client) CreateDeployment(deploy appsv1.Deployment) (*appsv1.Deployment, error) {
    92  	deployment, err := c.KubeClient.AppsV1().Deployments(c.Namespace).Create(context.TODO(), &deploy, metav1.CreateOptions{FieldManager: FieldManager})
    93  	if err != nil {
    94  		return nil, fmt.Errorf("unable to create Deployment %s: %w", deploy.Name, err)
    95  	}
    96  	return deployment, nil
    97  }
    98  
    99  // UpdateDeployment updates a deployment based on the given deployment spec
   100  func (c *Client) UpdateDeployment(deploy appsv1.Deployment) (*appsv1.Deployment, error) {
   101  	deployment, err := c.KubeClient.AppsV1().Deployments(c.Namespace).Update(context.TODO(), &deploy, metav1.UpdateOptions{FieldManager: FieldManager})
   102  	if err != nil {
   103  		return nil, fmt.Errorf("unable to update Deployment %s: %w", deploy.Name, err)
   104  	}
   105  	return deployment, nil
   106  }
   107  
   108  // ApplyDeployment creates or updates a deployment based on the given deployment spec
   109  // It is using force:true to make sure that if someone changed one of the values that odo manages,
   110  // odo overrides it with the value it expects instead of failing due to conflict.
   111  func (c *Client) ApplyDeployment(deploy appsv1.Deployment) (*appsv1.Deployment, error) {
   112  	data, err := json.Marshal(deploy)
   113  	if err != nil {
   114  		return nil, fmt.Errorf("unable to marshal deployment: %w", err)
   115  	}
   116  	klog.V(5).Infoln("Applying Deployment via server-side apply:")
   117  	klog.V(5).Infoln(resourceAsJson(deploy))
   118  
   119  	err = c.removeDuplicateEnv(deploy.Name)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	deployment, err := c.KubeClient.AppsV1().Deployments(c.Namespace).Patch(context.TODO(), deploy.Name, types.ApplyPatchType, data, metav1.PatchOptions{FieldManager: FieldManager, Force: Bool(true)})
   125  	if err != nil {
   126  		return nil, fmt.Errorf("unable to update Deployment %s: %w", deploy.Name, err)
   127  	}
   128  	return deployment, nil
   129  }
   130  
   131  // removeDuplicateEnv removes duplicate environment variables from containers, due to a bug in Service Binding Operator:
   132  // https://github.com/redhat-developer/service-binding-operator/issues/983
   133  func (c *Client) removeDuplicateEnv(deploymentName string) error {
   134  	deployment, err := c.KubeClient.AppsV1().Deployments(c.Namespace).Get(context.Background(), deploymentName, metav1.GetOptions{})
   135  	if kerrors.IsNotFound(err) {
   136  		return nil
   137  	}
   138  	if err != nil {
   139  		return err
   140  	}
   141  	changes := false
   142  	containers := deployment.Spec.Template.Spec.Containers
   143  	for i := range containers {
   144  		found := map[string]bool{}
   145  		var newEnv []corev1.EnvVar
   146  		for _, env := range containers[i].Env {
   147  			if _, ok := found[env.Name]; !ok {
   148  				found[env.Name] = true
   149  				newEnv = append(newEnv, env)
   150  			} else {
   151  				changes = true
   152  			}
   153  		}
   154  		containers[i].Env = newEnv
   155  	}
   156  	if changes {
   157  		_, err = c.KubeClient.AppsV1().Deployments(c.Namespace).Update(context.Background(), deployment, metav1.UpdateOptions{})
   158  		if kerrors.IsNotFound(err) {
   159  			return nil
   160  		}
   161  		return err
   162  	}
   163  	return nil
   164  }
   165  
   166  // GetDeploymentAPIVersion returns a map with Group, Version, Resource information of Deployment objects
   167  // depending on the GVR supported by the cluster
   168  func (c *Client) GetDeploymentAPIVersion() (schema.GroupVersionKind, error) {
   169  	extV1Beta1, err := c.IsDeploymentExtensionsV1Beta1()
   170  	if err != nil {
   171  		return schema.GroupVersionKind{}, err
   172  	}
   173  
   174  	if extV1Beta1 {
   175  		// this indicates we're running on OCP 3.11 cluster
   176  		return schema.GroupVersionKind{
   177  			Group:   "extensions",
   178  			Version: "v1beta1",
   179  			Kind:    "Deployment",
   180  		}, nil
   181  	}
   182  
   183  	return schema.GroupVersionKind{
   184  		Group:   "apps",
   185  		Version: "v1",
   186  		Kind:    "Deployment",
   187  	}, nil
   188  }
   189  
   190  func (c *Client) IsDeploymentExtensionsV1Beta1() (bool, error) {
   191  	return c.IsResourceSupported("extensions", "v1beta1", "deployments")
   192  }
   193  
   194  // DeploymentWatcher returns a watcher on Deployments into the current namespace
   195  // with the given label selector
   196  func (c *Client) DeploymentWatcher(ctx context.Context, selector string) (watch.Interface, error) {
   197  	ns := c.GetCurrentNamespace()
   198  	return c.GetClient().AppsV1().Deployments(ns).
   199  		Watch(ctx, metav1.ListOptions{
   200  			LabelSelector: selector,
   201  		})
   202  }
   203  

View as plain text