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

     1  package handler
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	pb "github.com/tickoalcantara12/micro/v3/proto/runtime"
     8  	"github.com/tickoalcantara12/micro/v3/service/auth"
     9  	"github.com/tickoalcantara12/micro/v3/service/errors"
    10  	"github.com/tickoalcantara12/micro/v3/service/events"
    11  	log "github.com/tickoalcantara12/micro/v3/service/logger"
    12  	"github.com/tickoalcantara12/micro/v3/service/runtime"
    13  	"github.com/tickoalcantara12/micro/v3/util/auth/namespace"
    14  )
    15  
    16  type Runtime struct {
    17  	Runtime runtime.Runtime
    18  }
    19  
    20  func setupServiceMeta(ctx context.Context, service *runtime.Service) {
    21  	if service.Metadata == nil {
    22  		service.Metadata = map[string]string{}
    23  	}
    24  	account, accOk := auth.AccountFromContext(ctx)
    25  	if accOk {
    26  		// Try to use the account name as it's more user friendly. If none, fall back to ID
    27  		owner := account.Name
    28  		if len(owner) == 0 {
    29  			owner = account.ID
    30  		}
    31  		service.Metadata["owner"] = owner
    32  		// This is a hack - we don't want vanilla `micro server` users where the auth is noop
    33  		// to have long uuid as owners, so we put micro here - not great, not terrible.
    34  		if auth.DefaultAuth.String() == "noop" {
    35  			service.Metadata["owner"] = "micro"
    36  		}
    37  		service.Metadata["group"] = account.Issuer
    38  	}
    39  	service.Metadata["started"] = time.Now().Format(time.RFC3339)
    40  }
    41  
    42  func (r *Runtime) Read(ctx context.Context, req *pb.ReadRequest, rsp *pb.ReadResponse) error {
    43  	// set defaults
    44  	if req.Options == nil {
    45  		req.Options = &pb.ReadOptions{}
    46  	}
    47  	if len(req.Options.Namespace) == 0 {
    48  		req.Options.Namespace = namespace.DefaultNamespace
    49  	}
    50  
    51  	// authorize the request
    52  	if err := namespace.Authorize(ctx, req.Options.Namespace, "runtime.Runtime.Read"); err != nil {
    53  		return err
    54  	}
    55  
    56  	// lookup the services
    57  	options := toReadOptions(ctx, req.Options)
    58  	services, err := r.Runtime.Read(options...)
    59  	if err != nil {
    60  		return errors.InternalServerError("runtime.Runtime.Read", err.Error())
    61  	}
    62  
    63  	// serialize the response
    64  	for _, service := range services {
    65  		rsp.Services = append(rsp.Services, toProto(service))
    66  	}
    67  
    68  	return nil
    69  }
    70  
    71  func (r *Runtime) Logs(ctx context.Context, req *pb.LogsRequest, stream pb.Runtime_LogsStream) error {
    72  	// set defaults
    73  	if req.Options == nil {
    74  		req.Options = &pb.LogsOptions{}
    75  	}
    76  	if len(req.Options.Namespace) == 0 {
    77  		req.Options.Namespace = namespace.DefaultNamespace
    78  	}
    79  
    80  	// authorize the request
    81  	if err := namespace.AuthorizeAdmin(ctx, req.Options.Namespace, "runtime.Runtime.Logs"); err != nil {
    82  		return err
    83  	}
    84  
    85  	opts := toLogsOptions(ctx, req.Options)
    86  
    87  	// options passed in the request
    88  	if req.Count > 0 {
    89  		opts = append(opts, runtime.LogsCount(req.Count))
    90  	}
    91  
    92  	if req.Stream {
    93  		opts = append(opts, runtime.LogsStream(req.Stream))
    94  	}
    95  
    96  	// request the logs from the backend
    97  	logStream, err := r.Runtime.Logs(&runtime.Service{
    98  		Name:    req.GetService(),
    99  		Version: req.GetVersion(),
   100  	}, opts...)
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	defer stream.Close()
   106  
   107  	// get the log stream itself
   108  	recordChan := logStream.Chan()
   109  
   110  	// when the context is cancelled aka timeout, notify of done
   111  	go func() {
   112  		for {
   113  			select {
   114  			case <-ctx.Done():
   115  				// stop the stream once done
   116  				logStream.Stop()
   117  				return
   118  			}
   119  		}
   120  	}()
   121  
   122  	// stream all records to completion
   123  	for record := range recordChan {
   124  		// send record
   125  		if err := stream.Send(&pb.LogRecord{
   126  			//Timestamp: record.Timestamp.Unix(),
   127  			Metadata: record.Metadata,
   128  			Message:  record.Message,
   129  		}); err != nil {
   130  			return err
   131  		}
   132  	}
   133  
   134  	return logStream.Error()
   135  }
   136  
   137  // Create a resource
   138  func (r *Runtime) Create(ctx context.Context, req *pb.CreateRequest, rsp *pb.CreateResponse) error {
   139  
   140  	// validate the request
   141  	if req.Resource == nil || (req.Resource.Namespace == nil && req.Resource.Networkpolicy == nil && req.Resource.Resourcequota == nil && req.Resource.Service == nil) {
   142  		return errors.BadRequest("runtime.Runtime.Create", "blank resource")
   143  	}
   144  
   145  	// set defaults
   146  	if req.Options == nil {
   147  		req.Options = &pb.CreateOptions{}
   148  	}
   149  	if len(req.Options.Namespace) == 0 {
   150  		req.Options.Namespace = namespace.DefaultNamespace
   151  	}
   152  
   153  	// authorize the request
   154  	if err := namespace.AuthorizeAdmin(ctx, req.Options.Namespace, "runtime.Runtime.Create"); err != nil {
   155  		return err
   156  	}
   157  
   158  	// Handle the different possible types of resource
   159  	switch {
   160  	case req.Resource.Namespace != nil:
   161  		ns, err := runtime.NewNamespace(req.Resource.Namespace.Name)
   162  		if err != nil {
   163  			return err
   164  		}
   165  
   166  		if err := r.Runtime.Create(ns, runtime.CreateNamespace(req.Resource.Namespace.Name)); err != nil {
   167  			return err
   168  		}
   169  
   170  		ev := &runtime.EventResourcePayload{
   171  			Type:      runtime.EventNamespaceCreated,
   172  			Namespace: ns.Name,
   173  		}
   174  
   175  		return events.Publish(runtime.EventTopic, ev, events.WithMetadata(map[string]string{
   176  			"type":      runtime.EventNamespaceCreated,
   177  			"namespace": ns.Name,
   178  		}))
   179  
   180  	case req.Resource.Networkpolicy != nil:
   181  		np, err := runtime.NewNetworkPolicy(req.Resource.Networkpolicy.Name, req.Resource.Networkpolicy.Namespace, req.Resource.Networkpolicy.Allowedlabels)
   182  		if err != nil {
   183  			return err
   184  		}
   185  
   186  		if err := r.Runtime.Create(np, runtime.CreateNamespace(req.Resource.Networkpolicy.Namespace)); err != nil {
   187  			return err
   188  		}
   189  
   190  		ev := &runtime.EventResourcePayload{
   191  			Type:          runtime.EventNetworkPolicyCreated,
   192  			Name:          np.Name,
   193  			Namespace:     np.Namespace,
   194  			NetworkPolicy: np,
   195  		}
   196  
   197  		return events.Publish(runtime.EventTopic, ev, events.WithMetadata(map[string]string{
   198  			"type":      ev.Type,
   199  			"namespace": ev.Namespace,
   200  		}))
   201  
   202  	case req.Resource.Resourcequota != nil:
   203  		rq, err := runtime.NewResourceQuota(
   204  			req.Resource.Resourcequota.Name,
   205  			req.Resource.Resourcequota.Namespace,
   206  			&runtime.Resources{
   207  				CPU:  int(req.Resource.Resourcequota.Requests.CPU),
   208  				Disk: int(req.Resource.Resourcequota.Requests.EphemeralStorage),
   209  				Mem:  int(req.Resource.Resourcequota.Requests.Memory),
   210  			},
   211  			&runtime.Resources{
   212  				CPU:  int(req.Resource.Resourcequota.Limits.CPU),
   213  				Disk: int(req.Resource.Resourcequota.Limits.EphemeralStorage),
   214  				Mem:  int(req.Resource.Resourcequota.Limits.Memory),
   215  			},
   216  		)
   217  		if err != nil {
   218  			return err
   219  		}
   220  
   221  		if err := r.Runtime.Create(rq, runtime.CreateNamespace(req.Resource.Resourcequota.Namespace)); err != nil {
   222  			return err
   223  		}
   224  
   225  		ev := &runtime.EventResourcePayload{
   226  			Type:      runtime.EventResourceQuotaCreated,
   227  			Name:      rq.Name,
   228  			Namespace: rq.Namespace,
   229  		}
   230  
   231  		return events.Publish(runtime.EventTopic, ev, events.WithMetadata(map[string]string{
   232  			"type":      ev.Type,
   233  			"namespace": ev.Namespace,
   234  		}))
   235  
   236  	case req.Resource.Service != nil:
   237  
   238  		// create the service
   239  		service := toService(req.Resource.Service)
   240  		setupServiceMeta(ctx, service)
   241  
   242  		options := toCreateOptions(ctx, req.Options)
   243  
   244  		log.Infof("Creating service %s version %s source %s", service.Name, service.Version, service.Source)
   245  		if err := r.Runtime.Create(service, options...); err != nil {
   246  			return errors.InternalServerError("runtime.Runtime.Create", err.Error())
   247  		}
   248  
   249  		// publish the create event
   250  		ev := &runtime.EventPayload{
   251  			Service:   service,
   252  			Namespace: req.Options.Namespace,
   253  			Type:      runtime.EventServiceCreated,
   254  		}
   255  
   256  		return events.Publish(runtime.EventTopic, ev, events.WithMetadata(map[string]string{
   257  			"type":      runtime.EventServiceCreated,
   258  			"namespace": req.Options.Namespace,
   259  		}))
   260  
   261  	default:
   262  		return nil
   263  	}
   264  }
   265  
   266  // Delete a resource
   267  func (r *Runtime) Delete(ctx context.Context, req *pb.DeleteRequest, rsp *pb.DeleteResponse) error {
   268  
   269  	// validate the request
   270  	if req.Resource == nil || (req.Resource.Namespace == nil && req.Resource.Networkpolicy == nil && req.Resource.Resourcequota == nil && req.Resource.Service == nil) {
   271  		return errors.BadRequest("runtime.Runtime.Delete", "blank resource")
   272  	}
   273  
   274  	// set defaults
   275  	if req.Options == nil {
   276  		req.Options = &pb.DeleteOptions{}
   277  	}
   278  	if len(req.Options.Namespace) == 0 {
   279  		req.Options.Namespace = namespace.DefaultNamespace
   280  	}
   281  
   282  	// authorize the request
   283  	if err := namespace.AuthorizeAdmin(ctx, req.Options.Namespace, "runtime.Runtime.Delete"); err != nil {
   284  		return err
   285  	}
   286  
   287  	// Handle the different possible types of resource
   288  	switch {
   289  	case req.Resource.Namespace != nil:
   290  		ns, err := runtime.NewNamespace(req.Resource.Namespace.Name)
   291  		if err != nil {
   292  			return err
   293  		}
   294  
   295  		if err := r.Runtime.Delete(ns, runtime.DeleteNamespace(req.Resource.Namespace.Name)); err != nil {
   296  			return err
   297  		}
   298  
   299  		ev := &runtime.EventResourcePayload{
   300  			Type:      runtime.EventNamespaceDeleted,
   301  			Namespace: ns.Name,
   302  		}
   303  
   304  		return events.Publish(runtime.EventTopic, ev, events.WithMetadata(map[string]string{
   305  			"type":      runtime.EventNamespaceDeleted,
   306  			"namespace": ns.Name,
   307  		}))
   308  
   309  	case req.Resource.Networkpolicy != nil:
   310  		np, err := runtime.NewNetworkPolicy(req.Resource.Networkpolicy.Name, req.Resource.Networkpolicy.Namespace, req.Resource.Networkpolicy.Allowedlabels)
   311  		if err != nil {
   312  			return err
   313  		}
   314  
   315  		if err := r.Runtime.Delete(np, runtime.DeleteNamespace(req.Resource.Networkpolicy.Namespace)); err != nil {
   316  			return err
   317  		}
   318  
   319  		ev := &runtime.EventResourcePayload{
   320  			Type:          runtime.EventNetworkPolicyDeleted,
   321  			Name:          np.Name,
   322  			Namespace:     np.Namespace,
   323  			NetworkPolicy: np,
   324  		}
   325  
   326  		return events.Publish(runtime.EventTopic, ev, events.WithMetadata(map[string]string{
   327  			"type":      ev.Type,
   328  			"namespace": ev.Namespace,
   329  		}))
   330  
   331  	case req.Resource.Resourcequota != nil:
   332  		rq, err := runtime.NewResourceQuota(
   333  			req.Resource.Resourcequota.Name,
   334  			req.Resource.Resourcequota.Namespace,
   335  			&runtime.Resources{},
   336  			&runtime.Resources{},
   337  		)
   338  		if err != nil {
   339  			return err
   340  		}
   341  
   342  		if err := r.Runtime.Delete(rq, runtime.DeleteNamespace(req.Resource.Resourcequota.Namespace)); err != nil {
   343  			return err
   344  		}
   345  
   346  		ev := &runtime.EventResourcePayload{
   347  			Type:      runtime.EventResourceQuotaDeleted,
   348  			Name:      rq.Name,
   349  			Namespace: rq.Namespace,
   350  		}
   351  
   352  		return events.Publish(runtime.EventTopic, ev, events.WithMetadata(map[string]string{
   353  			"type":      ev.Type,
   354  			"namespace": ev.Namespace,
   355  		}))
   356  
   357  	case req.Resource.Service != nil:
   358  
   359  		// delete the service
   360  		service := toService(req.Resource.Service)
   361  		options := toDeleteOptions(ctx, req.Options)
   362  
   363  		log.Infof("Deleting service %s version %s source %s", service.Name, service.Version, service.Source)
   364  		if err := r.Runtime.Delete(service, options...); err != nil {
   365  			return errors.InternalServerError("runtime.Runtime.Delete", err.Error())
   366  		}
   367  
   368  		// publish the delete event
   369  		ev := &runtime.EventPayload{
   370  			Type:      runtime.EventServiceDeleted,
   371  			Namespace: req.Options.Namespace,
   372  			Service:   service,
   373  		}
   374  
   375  		return events.Publish(runtime.EventTopic, ev, events.WithMetadata(map[string]string{
   376  			"type":      runtime.EventServiceDeleted,
   377  			"namespace": req.Options.Namespace,
   378  		}))
   379  
   380  	default:
   381  		return nil
   382  	}
   383  }
   384  
   385  // Update a resource
   386  func (r *Runtime) Update(ctx context.Context, req *pb.UpdateRequest, rsp *pb.UpdateResponse) error {
   387  
   388  	// validate the request
   389  	if req.Resource == nil || (req.Resource.Namespace == nil && req.Resource.Networkpolicy == nil && req.Resource.Resourcequota == nil && req.Resource.Service == nil) {
   390  		return errors.BadRequest("runtime.Runtime.Update", "blank resource")
   391  	}
   392  
   393  	// set defaults
   394  	if req.Options == nil {
   395  		req.Options = &pb.UpdateOptions{}
   396  	}
   397  	if len(req.Options.Namespace) == 0 {
   398  		req.Options.Namespace = namespace.DefaultNamespace
   399  	}
   400  
   401  	// authorize the request
   402  	if err := namespace.AuthorizeAdmin(ctx, req.Options.Namespace, "runtime.Runtime.Update"); err != nil {
   403  		return err
   404  	}
   405  
   406  	// Handle the different possible types of resource
   407  	switch {
   408  	case req.Resource.Namespace != nil:
   409  		// No updates to namespace
   410  		return nil
   411  
   412  	case req.Resource.Networkpolicy != nil:
   413  		np, err := runtime.NewNetworkPolicy(req.Resource.Networkpolicy.Name, req.Resource.Networkpolicy.Namespace, req.Resource.Networkpolicy.Allowedlabels)
   414  		if err != nil {
   415  			return err
   416  		}
   417  
   418  		if err := r.Runtime.Update(np, runtime.UpdateNamespace(req.Resource.Networkpolicy.Namespace)); err != nil {
   419  			return err
   420  		}
   421  
   422  		ev := &runtime.EventResourcePayload{
   423  			Type:          runtime.EventNetworkPolicyUpdated,
   424  			Name:          np.Name,
   425  			Namespace:     np.Namespace,
   426  			NetworkPolicy: np,
   427  		}
   428  
   429  		return events.Publish(runtime.EventTopic, ev, events.WithMetadata(map[string]string{
   430  			"type":      ev.Type,
   431  			"namespace": ev.Namespace,
   432  		}))
   433  
   434  	case req.Resource.Resourcequota != nil:
   435  		rq, err := runtime.NewResourceQuota(
   436  			req.Resource.Resourcequota.Name,
   437  			req.Resource.Resourcequota.Namespace,
   438  			&runtime.Resources{
   439  				CPU:  int(req.Resource.Resourcequota.Requests.CPU),
   440  				Disk: int(req.Resource.Resourcequota.Requests.EphemeralStorage),
   441  				Mem:  int(req.Resource.Resourcequota.Requests.Memory),
   442  			},
   443  			&runtime.Resources{
   444  				CPU:  int(req.Resource.Resourcequota.Limits.CPU),
   445  				Disk: int(req.Resource.Resourcequota.Limits.EphemeralStorage),
   446  				Mem:  int(req.Resource.Resourcequota.Limits.Memory),
   447  			},
   448  		)
   449  		if err != nil {
   450  			return err
   451  		}
   452  
   453  		if err := r.Runtime.Update(rq, runtime.UpdateNamespace(req.Resource.Resourcequota.Namespace)); err != nil {
   454  			return err
   455  		}
   456  
   457  		ev := &runtime.EventResourcePayload{
   458  			Type:      runtime.EventResourceQuotaUpdated,
   459  			Name:      rq.Name,
   460  			Namespace: rq.Namespace,
   461  		}
   462  
   463  		return events.Publish(runtime.EventTopic, ev, events.WithMetadata(map[string]string{
   464  			"type":      ev.Type,
   465  			"namespace": ev.Namespace,
   466  		}))
   467  
   468  	case req.Resource.Service != nil:
   469  
   470  		service := toService(req.Resource.Service)
   471  		setupServiceMeta(ctx, service)
   472  
   473  		options := toUpdateOptions(ctx, req.Options)
   474  
   475  		log.Infof("Updating service %s version %s source %s", service.Name, service.Version, service.Source)
   476  
   477  		if err := r.Runtime.Update(service, options...); err != nil {
   478  			return errors.InternalServerError("runtime.Runtime.Update", err.Error())
   479  		}
   480  
   481  		// publish the update event
   482  		ev := &runtime.EventPayload{
   483  			Service:   service,
   484  			Namespace: req.Options.Namespace,
   485  			Type:      runtime.EventServiceUpdated,
   486  		}
   487  
   488  		return events.Publish(runtime.EventTopic, ev, events.WithMetadata(map[string]string{
   489  			"type":      runtime.EventServiceUpdated,
   490  			"namespace": req.Options.Namespace,
   491  		}))
   492  
   493  	default:
   494  		return nil
   495  	}
   496  }