github.com/micro/go-micro/v2@v2.9.1/runtime/kubernetes/service.go (about)

     1  package kubernetes
     2  
     3  import (
     4  	"encoding/json"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/micro/go-micro/v2/logger"
     9  	"github.com/micro/go-micro/v2/runtime"
    10  	"github.com/micro/go-micro/v2/util/kubernetes/api"
    11  	"github.com/micro/go-micro/v2/util/kubernetes/client"
    12  )
    13  
    14  type service struct {
    15  	// service to manage
    16  	*runtime.Service
    17  	// Kubernetes service
    18  	kservice *client.Service
    19  	// Kubernetes deployment
    20  	kdeploy *client.Deployment
    21  }
    22  
    23  func parseError(err error) *api.Status {
    24  	status := new(api.Status)
    25  	json.Unmarshal([]byte(err.Error()), &status)
    26  	return status
    27  }
    28  
    29  func newService(s *runtime.Service, c runtime.CreateOptions) *service {
    30  	// use pre-formatted name/version
    31  	name := client.Format(s.Name)
    32  	version := client.Format(s.Version)
    33  
    34  	kservice := client.NewService(name, version, c.Type, c.Namespace)
    35  	kdeploy := client.NewDeployment(name, version, c.Type, c.Namespace)
    36  
    37  	// ensure the metadata is set
    38  	if kdeploy.Spec.Template.Metadata.Annotations == nil {
    39  		kdeploy.Spec.Template.Metadata.Annotations = make(map[string]string)
    40  	}
    41  
    42  	// create if non existent
    43  	if s.Metadata == nil {
    44  		s.Metadata = make(map[string]string)
    45  	}
    46  
    47  	// add the service metadata to the k8s labels, do this first so we
    48  	// don't override any labels used by the runtime, e.g. name
    49  	for k, v := range s.Metadata {
    50  		kdeploy.Metadata.Annotations[k] = v
    51  	}
    52  
    53  	// attach our values to the deployment; name, version, source
    54  	kdeploy.Metadata.Annotations["name"] = s.Name
    55  	kdeploy.Metadata.Annotations["version"] = s.Version
    56  	kdeploy.Metadata.Annotations["source"] = s.Source
    57  
    58  	// associate owner:group to be later augmented
    59  	kdeploy.Metadata.Annotations["owner"] = "micro"
    60  	kdeploy.Metadata.Annotations["group"] = "micro"
    61  
    62  	// update the deployment is a custom source is provided
    63  	if len(c.Image) > 0 {
    64  		for i := range kdeploy.Spec.Template.PodSpec.Containers {
    65  			kdeploy.Spec.Template.PodSpec.Containers[i].Image = c.Image
    66  			kdeploy.Spec.Template.PodSpec.Containers[i].Command = []string{}
    67  			kdeploy.Spec.Template.PodSpec.Containers[i].Args = []string{}
    68  		}
    69  	}
    70  
    71  	// define the environment values used by the container
    72  	env := make([]client.EnvVar, 0, len(c.Env))
    73  	for _, evar := range c.Env {
    74  		evarPair := strings.Split(evar, "=")
    75  		env = append(env, client.EnvVar{Name: evarPair[0], Value: evarPair[1]})
    76  	}
    77  
    78  	// if environment has been supplied update deployment default environment
    79  	if len(env) > 0 {
    80  		kdeploy.Spec.Template.PodSpec.Containers[0].Env = append(kdeploy.Spec.Template.PodSpec.Containers[0].Env, env...)
    81  	}
    82  
    83  	// set the command if specified
    84  	if len(c.Command) > 0 {
    85  		kdeploy.Spec.Template.PodSpec.Containers[0].Command = c.Command
    86  	}
    87  
    88  	if len(c.Args) > 0 {
    89  		kdeploy.Spec.Template.PodSpec.Containers[0].Args = c.Args
    90  	}
    91  
    92  	return &service{
    93  		Service:  s,
    94  		kservice: kservice,
    95  		kdeploy:  kdeploy,
    96  	}
    97  }
    98  
    99  func deploymentResource(d *client.Deployment) *client.Resource {
   100  	return &client.Resource{
   101  		Name:  d.Metadata.Name,
   102  		Kind:  "deployment",
   103  		Value: d,
   104  	}
   105  }
   106  
   107  func serviceResource(s *client.Service) *client.Resource {
   108  	return &client.Resource{
   109  		Name:  s.Metadata.Name,
   110  		Kind:  "service",
   111  		Value: s,
   112  	}
   113  }
   114  
   115  // Start starts the Kubernetes service. It creates new kubernetes deployment and service API objects
   116  func (s *service) Start(k client.Client, opts ...client.CreateOption) error {
   117  	// create deployment first; if we fail, we dont create service
   118  	if err := k.Create(deploymentResource(s.kdeploy), opts...); err != nil {
   119  		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   120  			logger.Debugf("Runtime failed to create deployment: %v", err)
   121  		}
   122  		s.Status("error", err)
   123  		v := parseError(err)
   124  		if v.Reason == "AlreadyExists" {
   125  			return runtime.ErrAlreadyExists
   126  		}
   127  		return err
   128  	}
   129  	// create service now that the deployment has been created
   130  	if err := k.Create(serviceResource(s.kservice), opts...); err != nil {
   131  		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   132  			logger.Debugf("Runtime failed to create service: %v", err)
   133  		}
   134  		s.Status("error", err)
   135  		v := parseError(err)
   136  		if v.Reason == "AlreadyExists" {
   137  			return runtime.ErrAlreadyExists
   138  		}
   139  		return err
   140  	}
   141  
   142  	s.Status("started", nil)
   143  
   144  	return nil
   145  }
   146  
   147  func (s *service) Stop(k client.Client, opts ...client.DeleteOption) error {
   148  	// first attempt to delete service
   149  	if err := k.Delete(serviceResource(s.kservice), opts...); err != nil {
   150  		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   151  			logger.Debugf("Runtime failed to delete service: %v", err)
   152  		}
   153  		s.Status("error", err)
   154  		return err
   155  	}
   156  	// delete deployment once the service has been deleted
   157  	if err := k.Delete(deploymentResource(s.kdeploy), opts...); err != nil {
   158  		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   159  			logger.Debugf("Runtime failed to delete deployment: %v", err)
   160  		}
   161  		s.Status("error", err)
   162  		return err
   163  	}
   164  
   165  	s.Status("stopped", nil)
   166  
   167  	return nil
   168  }
   169  
   170  func (s *service) Update(k client.Client, opts ...client.UpdateOption) error {
   171  	if err := k.Update(deploymentResource(s.kdeploy), opts...); err != nil {
   172  		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   173  			logger.Debugf("Runtime failed to update deployment: %v", err)
   174  		}
   175  		s.Status("error", err)
   176  		return err
   177  	}
   178  	if err := k.Update(serviceResource(s.kservice), opts...); err != nil {
   179  		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   180  			logger.Debugf("Runtime failed to update service: %v", err)
   181  		}
   182  		return err
   183  	}
   184  
   185  	return nil
   186  }
   187  
   188  func (s *service) Status(status string, err error) {
   189  	s.Metadata["lastStatusUpdate"] = time.Now().Format(time.RFC3339)
   190  	if err == nil {
   191  		s.Metadata["status"] = status
   192  		delete(s.Metadata, "error")
   193  		return
   194  	}
   195  	s.Metadata["status"] = "error"
   196  	s.Metadata["error"] = err.Error()
   197  }