github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/runtime/client/client.go (about)

     1  package client
     2  
     3  import (
     4  	"io"
     5  	"sync"
     6  
     7  	pb "github.com/tickoalcantara12/micro/v3/proto/runtime"
     8  	"github.com/tickoalcantara12/micro/v3/service/client"
     9  	"github.com/tickoalcantara12/micro/v3/service/context"
    10  	"github.com/tickoalcantara12/micro/v3/service/runtime"
    11  )
    12  
    13  type svc struct {
    14  	sync.RWMutex
    15  	options runtime.Options
    16  	runtime pb.RuntimeService
    17  }
    18  
    19  // Init initializes runtime with given options
    20  func (s *svc) Init(opts ...runtime.Option) error {
    21  	s.Lock()
    22  	defer s.Unlock()
    23  
    24  	for _, o := range opts {
    25  		o(&s.options)
    26  	}
    27  
    28  	s.runtime = pb.NewRuntimeService("runtime", client.DefaultClient)
    29  
    30  	return nil
    31  }
    32  
    33  // Create a resource
    34  func (s *svc) Create(resource runtime.Resource, opts ...runtime.CreateOption) error {
    35  	var options runtime.CreateOptions
    36  	for _, o := range opts {
    37  		o(&options)
    38  	}
    39  
    40  	// Handle the various different types of resources:
    41  	switch resource.Type() {
    42  	case runtime.TypeNamespace:
    43  
    44  		// Assert the resource back into a *runtime.Namespace
    45  		namespace, ok := resource.(*runtime.Namespace)
    46  		if !ok {
    47  			return runtime.ErrInvalidResource
    48  		}
    49  
    50  		// Allow the options to take precedence
    51  		if options.Namespace != "" {
    52  			namespace.Name = options.Namespace
    53  		}
    54  
    55  		// runtime namespace create request
    56  		req := &pb.CreateRequest{
    57  			Resource: &pb.Resource{
    58  				Namespace: &pb.Namespace{
    59  					Name: namespace.Name,
    60  				},
    61  			},
    62  		}
    63  
    64  		if _, err := s.runtime.Create(context.DefaultContext, req, client.WithAuthToken()); err != nil {
    65  			return err
    66  		}
    67  
    68  	case runtime.TypeNetworkPolicy:
    69  
    70  		// Assert the resource back into a *runtime.NetworkPolicy
    71  		networkPolicy, ok := resource.(*runtime.NetworkPolicy)
    72  		if !ok {
    73  			return runtime.ErrInvalidResource
    74  		}
    75  
    76  		// Allow the options to take precedence
    77  		if options.Namespace != "" {
    78  			networkPolicy.Namespace = options.Namespace
    79  		}
    80  
    81  		// runtime namespace create request
    82  		req := &pb.CreateRequest{
    83  			Resource: &pb.Resource{
    84  				Networkpolicy: &pb.NetworkPolicy{
    85  					Name:      networkPolicy.Name,
    86  					Namespace: networkPolicy.Namespace,
    87  				},
    88  			},
    89  		}
    90  
    91  		if _, err := s.runtime.Create(context.DefaultContext, req, client.WithAuthToken()); err != nil {
    92  			return err
    93  		}
    94  
    95  	case runtime.TypeResourceQuota:
    96  
    97  		// Assert the resource back into a *runtime.ResourceQuota
    98  		resourceQuota, ok := resource.(*runtime.ResourceQuota)
    99  		if !ok {
   100  			return runtime.ErrInvalidResource
   101  		}
   102  
   103  		// Allow the options to take precedence
   104  		if options.Namespace != "" {
   105  			resourceQuota.Namespace = options.Namespace
   106  		}
   107  
   108  		// runtime namespace create request
   109  		req := &pb.CreateRequest{
   110  			Resource: &pb.Resource{
   111  				Resourcequota: &pb.ResourceQuota{
   112  					Requests: &pb.Resources{
   113  						CPU:              int32(resourceQuota.Requests.CPU),
   114  						EphemeralStorage: int32(resourceQuota.Requests.Disk),
   115  						Memory:           int32(resourceQuota.Requests.Mem),
   116  					},
   117  					Limits: &pb.Resources{
   118  						CPU:              int32(resourceQuota.Limits.CPU),
   119  						EphemeralStorage: int32(resourceQuota.Limits.Disk),
   120  						Memory:           int32(resourceQuota.Limits.Mem),
   121  					},
   122  					Name:      resourceQuota.Name,
   123  					Namespace: resourceQuota.Namespace,
   124  				},
   125  			},
   126  		}
   127  
   128  		if _, err := s.runtime.Create(context.DefaultContext, req, client.WithAuthToken()); err != nil {
   129  			return err
   130  		}
   131  
   132  	case runtime.TypeService:
   133  
   134  		// Assert the resource back into a *runtime.Service
   135  		svc, ok := resource.(*runtime.Service)
   136  		if !ok {
   137  			return runtime.ErrInvalidResource
   138  		}
   139  
   140  		// set the default source from MICRO_RUNTIME_SOURCE
   141  		if len(svc.Source) == 0 {
   142  			svc.Source = s.options.Source
   143  		}
   144  
   145  		// runtime service create request
   146  		req := &pb.CreateRequest{
   147  			Resource: &pb.Resource{
   148  				Service: &pb.Service{
   149  					Name:     svc.Name,
   150  					Version:  svc.Version,
   151  					Source:   svc.Source,
   152  					Metadata: svc.Metadata,
   153  				},
   154  			},
   155  			Options: &pb.CreateOptions{
   156  				Command:    options.Command,
   157  				Args:       options.Args,
   158  				Env:        options.Env,
   159  				Type:       options.Type,
   160  				Image:      options.Image,
   161  				Namespace:  options.Namespace,
   162  				Secrets:    options.Secrets,
   163  				Entrypoint: options.Entrypoint,
   164  				Volumes:    options.Volumes,
   165  				Instances:  int64(options.Instances),
   166  				Force:      options.Force,
   167  			},
   168  		}
   169  
   170  		if _, err := s.runtime.Create(context.DefaultContext, req, client.WithAuthToken()); err != nil {
   171  			return err
   172  		}
   173  	default:
   174  		return runtime.ErrInvalidResource
   175  	}
   176  
   177  	return nil
   178  }
   179  
   180  func (s *svc) Logs(resource runtime.Resource, options ...runtime.LogsOption) (runtime.LogStream, error) {
   181  	var opts runtime.LogsOptions
   182  	for _, o := range options {
   183  		o(&opts)
   184  	}
   185  
   186  	// Handle the various different types of resources:
   187  	switch resource.Type() {
   188  	case runtime.TypeNamespace:
   189  		// noop (Namespace is not supported by *kubernetes.Logs())
   190  		return nil, nil
   191  	case runtime.TypeNetworkPolicy:
   192  		// noop (NetworkPolicy is not supported by *kubernetes.Logs())
   193  		return nil, nil
   194  	case runtime.TypeResourceQuota:
   195  		// noop (ResourceQuota is not supported by *kubernetes.Logs())
   196  		return nil, nil
   197  	case runtime.TypeService:
   198  		// Assert the resource back into a *runtime.Service
   199  		service, ok := resource.(*runtime.Service)
   200  		if !ok {
   201  			return nil, runtime.ErrInvalidResource
   202  		}
   203  
   204  		ls, err := s.runtime.Logs(context.DefaultContext, &pb.LogsRequest{
   205  			Service: service.Name,
   206  			Version: service.Version,
   207  			Stream:  opts.Stream,
   208  			Count:   opts.Count,
   209  			Options: &pb.LogsOptions{
   210  				Namespace: opts.Namespace,
   211  			},
   212  		}, client.WithAuthToken())
   213  		if err != nil {
   214  			return nil, err
   215  		}
   216  
   217  		logStream := &serviceLogs{
   218  			service: service.Name,
   219  			version: service.Version,
   220  			stream:  make(chan runtime.Log),
   221  			stop:    make(chan bool),
   222  		}
   223  
   224  		go func() {
   225  			for {
   226  				select {
   227  				// @todo this never seems to return, investigate
   228  				case <-ls.Context().Done():
   229  					logStream.Stop()
   230  				}
   231  			}
   232  		}()
   233  
   234  		go func() {
   235  			for {
   236  				select {
   237  				// @todo this never seems to return, investigate
   238  				case <-ls.Context().Done():
   239  					close(logStream.stream)
   240  					return
   241  				case <-logStream.stop:
   242  					close(logStream.stream)
   243  					return
   244  				default:
   245  					record := pb.LogRecord{}
   246  
   247  					if err := ls.RecvMsg(&record); err != nil {
   248  						if err != io.EOF {
   249  							logStream.err = err
   250  						}
   251  						close(logStream.stream)
   252  						logStream.Stop()
   253  						return
   254  					}
   255  
   256  					logStream.stream <- runtime.Log{
   257  						Message:  record.GetMessage(),
   258  						Metadata: record.GetMetadata(),
   259  					}
   260  				}
   261  			}
   262  		}()
   263  
   264  		return logStream, nil
   265  	default:
   266  		return nil, runtime.ErrInvalidResource
   267  	}
   268  }
   269  
   270  type serviceLogs struct {
   271  	service string
   272  	version string
   273  	stream  chan runtime.Log
   274  
   275  	sync.Mutex
   276  	stop chan bool
   277  	err  error
   278  }
   279  
   280  func (l *serviceLogs) Error() error {
   281  	return l.err
   282  }
   283  
   284  func (l *serviceLogs) Chan() chan runtime.Log {
   285  	return l.stream
   286  }
   287  
   288  func (l *serviceLogs) Stop() error {
   289  	l.Lock()
   290  	defer l.Unlock()
   291  	select {
   292  	case <-l.stop:
   293  		return nil
   294  	default:
   295  		close(l.stop)
   296  	}
   297  	return nil
   298  }
   299  
   300  // Read returns the service with the given name from the runtime
   301  func (s *svc) Read(opts ...runtime.ReadOption) ([]*runtime.Service, error) {
   302  	var options runtime.ReadOptions
   303  	for _, o := range opts {
   304  		o(&options)
   305  	}
   306  
   307  	// runtime service create request
   308  	req := &pb.ReadRequest{
   309  		Options: &pb.ReadOptions{
   310  			Service:   options.Service,
   311  			Version:   options.Version,
   312  			Type:      options.Type,
   313  			Namespace: options.Namespace,
   314  		},
   315  	}
   316  
   317  	resp, err := s.runtime.Read(context.DefaultContext, req, client.WithAuthToken())
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  
   322  	services := make([]*runtime.Service, 0, len(resp.Services))
   323  	for _, service := range resp.Services {
   324  		svc := &runtime.Service{
   325  			Name:     service.Name,
   326  			Version:  service.Version,
   327  			Source:   service.Source,
   328  			Metadata: service.Metadata,
   329  			Status:   runtime.ServiceStatus(service.Status),
   330  		}
   331  		services = append(services, svc)
   332  	}
   333  
   334  	return services, nil
   335  }
   336  
   337  // Update a resource
   338  func (s *svc) Update(resource runtime.Resource, opts ...runtime.UpdateOption) error {
   339  	var options runtime.UpdateOptions
   340  	for _, o := range opts {
   341  		o(&options)
   342  	}
   343  
   344  	// Handle the various different types of resources:
   345  	switch resource.Type() {
   346  	case runtime.TypeNamespace:
   347  
   348  		// Assert the resource back into a *runtime.Namespace
   349  		namespace, ok := resource.(*runtime.Namespace)
   350  		if !ok {
   351  			return runtime.ErrInvalidResource
   352  		}
   353  
   354  		// Allow the options to take precedence
   355  		if options.Namespace != "" {
   356  			namespace.Name = options.Namespace
   357  		}
   358  
   359  		// runtime namespace update request
   360  		req := &pb.UpdateRequest{
   361  			Resource: &pb.Resource{
   362  				Namespace: &pb.Namespace{
   363  					Name: namespace.Name,
   364  				},
   365  			},
   366  		}
   367  
   368  		if _, err := s.runtime.Update(context.DefaultContext, req, client.WithAuthToken()); err != nil {
   369  			return err
   370  		}
   371  
   372  	case runtime.TypeNetworkPolicy:
   373  
   374  		// Assert the resource back into a *runtime.NetworkPolicy
   375  		networkPolicy, ok := resource.(*runtime.NetworkPolicy)
   376  		if !ok {
   377  			return runtime.ErrInvalidResource
   378  		}
   379  
   380  		// Allow the options to take precedence
   381  		if options.Namespace != "" {
   382  			networkPolicy.Namespace = options.Namespace
   383  		}
   384  
   385  		// runtime networkpolicy update request
   386  		req := &pb.UpdateRequest{
   387  			Resource: &pb.Resource{
   388  				Networkpolicy: &pb.NetworkPolicy{
   389  					Name:      networkPolicy.Name,
   390  					Namespace: networkPolicy.Namespace,
   391  				},
   392  			},
   393  		}
   394  
   395  		if _, err := s.runtime.Update(context.DefaultContext, req, client.WithAuthToken()); err != nil {
   396  			return err
   397  		}
   398  
   399  	case runtime.TypeResourceQuota:
   400  
   401  		// Assert the resource back into a *runtime.ResourceQuota
   402  		resourceQuota, ok := resource.(*runtime.ResourceQuota)
   403  		if !ok {
   404  			return runtime.ErrInvalidResource
   405  		}
   406  
   407  		// Allow the options to take precedence
   408  		if options.Namespace != "" {
   409  			resourceQuota.Namespace = options.Namespace
   410  		}
   411  
   412  		// runtime resourcequota update request
   413  		req := &pb.UpdateRequest{
   414  			Resource: &pb.Resource{
   415  				Resourcequota: &pb.ResourceQuota{
   416  					Requests: &pb.Resources{
   417  						CPU:              int32(resourceQuota.Requests.CPU),
   418  						EphemeralStorage: int32(resourceQuota.Requests.Disk),
   419  						Memory:           int32(resourceQuota.Requests.Mem),
   420  					},
   421  					Limits: &pb.Resources{
   422  						CPU:              int32(resourceQuota.Limits.CPU),
   423  						EphemeralStorage: int32(resourceQuota.Limits.Disk),
   424  						Memory:           int32(resourceQuota.Limits.Mem),
   425  					},
   426  					Name:      resourceQuota.Name,
   427  					Namespace: resourceQuota.Namespace,
   428  				},
   429  			},
   430  		}
   431  
   432  		if _, err := s.runtime.Update(context.DefaultContext, req, client.WithAuthToken()); err != nil {
   433  			return err
   434  		}
   435  
   436  	case runtime.TypeService:
   437  
   438  		// Assert the resource back into a *runtime.Service
   439  		svc, ok := resource.(*runtime.Service)
   440  		if !ok {
   441  			return runtime.ErrInvalidResource
   442  		}
   443  
   444  		// runtime service create request
   445  		req := &pb.UpdateRequest{
   446  			Resource: &pb.Resource{
   447  				Service: &pb.Service{
   448  					Name:     svc.Name,
   449  					Version:  svc.Version,
   450  					Source:   svc.Source,
   451  					Metadata: svc.Metadata,
   452  				},
   453  			},
   454  			Options: &pb.UpdateOptions{
   455  				Namespace:  options.Namespace,
   456  				Entrypoint: options.Entrypoint,
   457  				Instances:  int64(options.Instances),
   458  			},
   459  		}
   460  
   461  		if _, err := s.runtime.Update(context.DefaultContext, req, client.WithAuthToken()); err != nil {
   462  			return err
   463  		}
   464  	default:
   465  		return runtime.ErrInvalidResource
   466  	}
   467  
   468  	return nil
   469  }
   470  
   471  // Delete a resource
   472  func (s *svc) Delete(resource runtime.Resource, opts ...runtime.DeleteOption) error {
   473  	var options runtime.DeleteOptions
   474  	for _, o := range opts {
   475  		o(&options)
   476  	}
   477  
   478  	// Handle the various different types of resources:
   479  	switch resource.Type() {
   480  	case runtime.TypeNamespace:
   481  
   482  		// Assert the resource back into a *runtime.Namespace
   483  		namespace, ok := resource.(*runtime.Namespace)
   484  		if !ok {
   485  			return runtime.ErrInvalidResource
   486  		}
   487  
   488  		// Allow the options to take precedence
   489  		if options.Namespace != "" {
   490  			namespace.Name = options.Namespace
   491  		}
   492  
   493  		// runtime namespace delete request
   494  		req := &pb.DeleteRequest{
   495  			Resource: &pb.Resource{
   496  				Namespace: &pb.Namespace{
   497  					Name: namespace.Name,
   498  				},
   499  			},
   500  		}
   501  
   502  		if _, err := s.runtime.Delete(context.DefaultContext, req, client.WithAuthToken()); err != nil {
   503  			return err
   504  		}
   505  
   506  	case runtime.TypeNetworkPolicy:
   507  
   508  		// Assert the resource back into a *runtime.NetworkPolicy
   509  		networkPolicy, ok := resource.(*runtime.NetworkPolicy)
   510  		if !ok {
   511  			return runtime.ErrInvalidResource
   512  		}
   513  
   514  		// Allow the options to take precedence
   515  		if options.Namespace != "" {
   516  			networkPolicy.Namespace = options.Namespace
   517  		}
   518  
   519  		// runtime namespace delete request
   520  		req := &pb.DeleteRequest{
   521  			Resource: &pb.Resource{
   522  				Networkpolicy: &pb.NetworkPolicy{
   523  					Name:      networkPolicy.Name,
   524  					Namespace: networkPolicy.Namespace,
   525  				},
   526  			},
   527  		}
   528  
   529  		if _, err := s.runtime.Delete(context.DefaultContext, req, client.WithAuthToken()); err != nil {
   530  			return err
   531  		}
   532  
   533  	case runtime.TypeResourceQuota:
   534  
   535  		// Assert the resource back into a *runtime.ResourceQuota
   536  		resourceQuota, ok := resource.(*runtime.ResourceQuota)
   537  		if !ok {
   538  			return runtime.ErrInvalidResource
   539  		}
   540  
   541  		// Allow the options to take precedence
   542  		if options.Namespace != "" {
   543  			resourceQuota.Namespace = options.Namespace
   544  		}
   545  
   546  		// runtime resourcequota delete request
   547  		req := &pb.DeleteRequest{
   548  			Resource: &pb.Resource{
   549  				Resourcequota: &pb.ResourceQuota{
   550  					Requests: &pb.Resources{
   551  						CPU:              int32(resourceQuota.Requests.CPU),
   552  						EphemeralStorage: int32(resourceQuota.Requests.Disk),
   553  						Memory:           int32(resourceQuota.Requests.Mem),
   554  					},
   555  					Limits: &pb.Resources{
   556  						CPU:              int32(resourceQuota.Limits.CPU),
   557  						EphemeralStorage: int32(resourceQuota.Limits.Disk),
   558  						Memory:           int32(resourceQuota.Limits.Mem),
   559  					},
   560  					Name:      resourceQuota.Name,
   561  					Namespace: resourceQuota.Namespace,
   562  				},
   563  			},
   564  		}
   565  
   566  		if _, err := s.runtime.Delete(context.DefaultContext, req, client.WithAuthToken()); err != nil {
   567  			return err
   568  		}
   569  
   570  	case runtime.TypeService:
   571  
   572  		// Assert the resource back into a *runtime.Service
   573  		svc, ok := resource.(*runtime.Service)
   574  		if !ok {
   575  			return runtime.ErrInvalidResource
   576  		}
   577  
   578  		// runtime service delete request
   579  		req := &pb.DeleteRequest{
   580  			Resource: &pb.Resource{
   581  				Service: &pb.Service{
   582  					Name:     svc.Name,
   583  					Version:  svc.Version,
   584  					Source:   svc.Source,
   585  					Metadata: svc.Metadata,
   586  				},
   587  			},
   588  			Options: &pb.DeleteOptions{
   589  				Namespace: options.Namespace,
   590  			},
   591  		}
   592  
   593  		if _, err := s.runtime.Delete(context.DefaultContext, req, client.WithAuthToken()); err != nil {
   594  			return err
   595  		}
   596  	default:
   597  		return runtime.ErrInvalidResource
   598  	}
   599  
   600  	return nil
   601  }
   602  
   603  // Start starts the runtime
   604  func (s *svc) Start() error {
   605  	// NOTE: nothing to be done here
   606  	return nil
   607  }
   608  
   609  // Stop stops the runtime
   610  func (s *svc) Stop() error {
   611  	// NOTE: nothing to be done here
   612  	return nil
   613  }
   614  
   615  // Returns the runtime service implementation
   616  func (s *svc) String() string {
   617  	return "service"
   618  }
   619  
   620  // NewRuntime creates new service runtime and returns it
   621  func NewRuntime(opts ...runtime.Option) runtime.Runtime {
   622  	var options runtime.Options
   623  	for _, o := range opts {
   624  		o(&options)
   625  	}
   626  
   627  	return &svc{
   628  		options: options,
   629  		runtime: pb.NewRuntimeService("runtime", client.DefaultClient),
   630  	}
   631  }