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 }