From ddd82a77e68d5cdf7d172d1e3b8aff814841233e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 18 Nov 2023 17:09:00 +0000 Subject: [PATCH 1/3] [GithubActions] Update Restate '0.5.0' SDK-Typescript '0.4.1' Tour '0.4.1' (#179) Co-authored-by: jackkleeman --- docs/references/errors.md | 10 +- restate.config.json | 6 +- static/schemas/config_schema.json | 212 ++++++++++++++++++++++-------- static/schemas/openapi-meta.json | 2 +- static/schemas/restate.yaml | 25 +++- 5 files changed, 190 insertions(+), 65 deletions(-) diff --git a/docs/references/errors.md b/docs/references/errors.md index 386f5bf7..40fac500 100644 --- a/docs/references/errors.md +++ b/docs/references/errors.md @@ -170,6 +170,14 @@ This is a generic error which can be caused by many reasons, including: * Non-deterministic user code execution * Restate runtime and/or SDK bug -In some cases, the error will be retried depending on the configured retry policy. We suggest checking the service endpoint logs as well to get any hint on the error cause. +## RT0007 + +A retry-able error was received from the service endpoint while processing the invocation. + +Suggestions: + +* Check the service endpoint logs to get more info about the error cause, like the stacktrace. +* Look at the https://docs.restate.dev/services/sdk/error-handling for more info about error handling in services. + diff --git a/restate.config.json b/restate.config.json index e9f90bba..ad926128 100644 --- a/restate.config.json +++ b/restate.config.json @@ -1,5 +1,5 @@ { - "RESTATE_DIST_VERSION": "0.4.0", - "TYPESCRIPT_SDK_VERSION": "0.4.0", - "TOUR_VERSION": "0.4.0" + "RESTATE_DIST_VERSION": "0.5.0", + "TYPESCRIPT_SDK_VERSION": "0.4.1", + "TOUR_VERSION": "0.4.1" } \ No newline at end of file diff --git a/static/schemas/config_schema.json b/static/schemas/config_schema.json index 643be66a..6e7229da 100644 --- a/static/schemas/config_schema.json +++ b/static/schemas/config_schema.json @@ -23,9 +23,21 @@ }, "meta": { "default": { - "proxy_uri": null, "rest_address": "0.0.0.0:9070", "rest_concurrency_limit": 1000, + "service_client": { + "http": { + "keep_alive_options": { + "interval": "40s", + "timeout": "20s" + }, + "proxy_uri": null + }, + "lambda": { + "assume_role_external_id": null, + "aws_profile": null + } + }, "storage_path": "target/meta/" }, "$ref": "#/definitions/MetaOptions" @@ -48,14 +60,9 @@ "abort_timeout": "1m", "concurrency_limit": null, "disable_eager_state": false, - "http2_keep_alive": { - "interval": "40s", - "timeout": "20s" - }, "inactivity_timeout": "1m", "message_size_limit": null, "message_size_warning": 10485760, - "proxy_uri": null, "retry_policy": { "factor": 2.0, "initial_interval": "50ms", @@ -63,7 +70,20 @@ "max_interval": "10s", "type": "Exponential" }, - "tmp_dir": "/tmp/invoker-018b61e3cdd37d32afb3778d3a16f978" + "service_client": { + "http": { + "keep_alive_options": { + "interval": "40s", + "timeout": "20s" + }, + "proxy_uri": null + }, + "lambda": { + "assume_role_external_id": null, + "aws_profile": null + } + }, + "tmp_dir": "/tmp/invoker-018be364379f7668aa8c7002abe22679" }, "kafka": { "clusters": {} @@ -236,6 +256,67 @@ "default": "target/meta/", "type": "string" }, + "service_client": { + "default": { + "http": { + "keep_alive_options": { + "interval": "40s", + "timeout": "20s" + }, + "proxy_uri": null + }, + "lambda": { + "assume_role_external_id": null, + "aws_profile": null + } + }, + "$ref": "#/definitions/ServiceClientOptions" + } + } + }, + "ServiceClientOptions": { + "title": "Client options", + "type": "object", + "properties": { + "http": { + "default": { + "keep_alive_options": { + "interval": "40s", + "timeout": "20s" + }, + "proxy_uri": null + }, + "$ref": "#/definitions/HttpClientOptions" + }, + "lambda": { + "default": { + "assume_role_external_id": null, + "aws_profile": null + }, + "$ref": "#/definitions/LambdaClientOptions" + } + } + }, + "HttpClientOptions": { + "title": "HTTP client options", + "type": "object", + "properties": { + "keep_alive_options": { + "title": "HTTP/2 Keep-alive", + "description": "Configuration for the HTTP/2 keep-alive mechanism, using PING frames. If unset, HTTP/2 keep-alive are disabled.", + "default": { + "interval": "40s", + "timeout": "20s" + }, + "anyOf": [ + { + "$ref": "#/definitions/Http2KeepAliveOptions" + }, + { + "type": "null" + } + ] + }, "proxy_uri": { "title": "Proxy URI", "description": "A URI, such as `http://127.0.0.1:10001`, of a server to which all invocations should be sent, with the `Host` header set to the service endpoint URI. HTTPS proxy URIs are supported, but only HTTP endpoint traffic will be proxied currently. Can be overridden by the `HTTP_PROXY` environment variable.", @@ -247,6 +328,49 @@ } } }, + "Http2KeepAliveOptions": { + "title": "HTTP/2 Keep alive options", + "description": "Configuration for the HTTP/2 keep-alive mechanism, using PING frames.\n\nPlease note: most gateways don't propagate the HTTP/2 keep-alive between downstream and upstream hosts. In those environments, you need to make sure the gateway can detect a broken connection to the upstream service endpoint(s).", + "type": "object", + "properties": { + "interval": { + "title": "HTTP/2 Keep-alive interval", + "description": "Sets an interval for HTTP/2 PING frames should be sent to keep a connection alive.\n\nYou should set this timeout with a value lower than the `response_abort_timeout`.", + "default": "40s", + "type": "string" + }, + "timeout": { + "title": "Timeout", + "description": "Sets a timeout for receiving an acknowledgement of the keep-alive ping.\n\nIf the ping is not acknowledged within the timeout, the connection will be closed.", + "default": "20s", + "type": "string" + } + } + }, + "LambdaClientOptions": { + "title": "Lambda client options", + "type": "object", + "properties": { + "aws_profile": { + "title": "AWS Profile", + "description": "Name of the AWS profile to select. Defaults to 'AWS_PROFILE' env var, or otherwise the `default` profile.", + "default": null, + "type": [ + "string", + "null" + ] + }, + "assume_role_external_id": { + "title": "AssumeRole external ID", + "description": "An external ID to apply to any AssumeRole operations taken by this client. https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html Can be overridden by the `AWS_EXTERNAL_ID` environment variable.", + "default": null, + "type": [ + "string", + "null" + ] + } + } + }, "WorkerOptions": { "title": "Worker options", "type": "object", @@ -320,14 +444,9 @@ "abort_timeout": "1m", "concurrency_limit": null, "disable_eager_state": false, - "http2_keep_alive": { - "interval": "40s", - "timeout": "20s" - }, "inactivity_timeout": "1m", "message_size_limit": null, "message_size_warning": 10485760, - "proxy_uri": null, "retry_policy": { "factor": 2.0, "initial_interval": "50ms", @@ -335,7 +454,20 @@ "max_interval": "10s", "type": "Exponential" }, - "tmp_dir": "/tmp/invoker-018b61e3cdd478fb97b33ca818803b12" + "service_client": { + "http": { + "keep_alive_options": { + "interval": "40s", + "timeout": "20s" + }, + "proxy_uri": null + }, + "lambda": { + "assume_role_external_id": null, + "aws_profile": null + } + }, + "tmp_dir": "/tmp/invoker-018be36437a07c5ebd79ae258664245c" }, "$ref": "#/definitions/InvokerOptions" }, @@ -626,19 +758,10 @@ "format": "uint", "minimum": 0.0 }, - "proxy_uri": { - "title": "Proxy URI", - "description": "A URI, such as `http://127.0.0.1:10001`, of a server to which all invocations should be sent, with the `Host` header set to the service endpoint URI. HTTPS proxy URIs are supported, but only HTTP endpoint traffic will be proxied currently. Can be overridden by the `HTTP_PROXY` environment variable.", - "default": null, - "type": [ - "string", - "null" - ] - }, "tmp_dir": { "title": "Temporary directory", "description": "Temporary directory to use for the invoker temporary files. If empty, the system temporary directory will be used instead.", - "default": "/tmp/invoker-018b61e3cdd4707aa23d3cb228a60c52", + "default": "/tmp/invoker-018be36437a07e34940810acf9df397d", "type": "string" }, "concurrency_limit": { @@ -652,21 +775,21 @@ "format": "uint", "minimum": 0.0 }, - "http2_keep_alive": { - "title": "HTTP/2 Keep-alive", - "description": "Configuration for the HTTP/2 keep-alive mechanism, using PING frames. If unset, HTTP/2 keep-alive are disabled.", + "service_client": { "default": { - "interval": "40s", - "timeout": "20s" - }, - "anyOf": [ - { - "$ref": "#/definitions/Http2KeepAliveOptions" + "http": { + "keep_alive_options": { + "interval": "40s", + "timeout": "20s" + }, + "proxy_uri": null }, - { - "type": "null" + "lambda": { + "assume_role_external_id": null, + "aws_profile": null } - ] + }, + "$ref": "#/definitions/ServiceClientOptions" } } }, @@ -767,25 +890,6 @@ } ] }, - "Http2KeepAliveOptions": { - "title": "HTTP/2 Keep alive options", - "description": "Configuration for the HTTP/2 keep-alive mechanism, using PING frames.\n\nPlease note: most gateways don't propagate the HTTP/2 keep-alive between downstream and upstream hosts. In those environments, you need to make sure the gateway can detect a broken connection to the upstream service endpoint(s).", - "type": "object", - "properties": { - "interval": { - "title": "HTTP/2 Keep-alive interval", - "description": "Sets an interval for HTTP/2 PING frames should be sent to keep a connection alive.\n\nYou should set this timeout with a value lower than the `response_abort_timeout`.", - "default": "40s", - "type": "string" - }, - "timeout": { - "title": "Timeout", - "description": "Sets a timeout for receiving an acknowledgement of the keep-alive ping.\n\nIf the ping is not acknowledged within the timeout, the connection will be closed.", - "default": "20s", - "type": "string" - } - } - }, "Options": { "title": "Runtime options", "description": "Configuration for the Tokio runtime used by Restate.", diff --git a/static/schemas/openapi-meta.json b/static/schemas/openapi-meta.json index 22ee6fb0..c010ef41 100644 --- a/static/schemas/openapi-meta.json +++ b/static/schemas/openapi-meta.json @@ -1 +1 @@ -{"openapi":"3.0.0","info":{"title":"Admin API","version":"0.4.0"},"paths":{"/services/{service}/methods":{"get":{"tags":["service_method"],"summary":"List service methods","description":"List all the methods of the given service.","operationId":"list_service_methods","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListServiceMethodsResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/endpoints/{endpoint}":{"delete":{"tags":["service_endpoint"],"summary":"Delete service endpoint","description":"Delete service endpoint. Currently it's supported to remove a service endpoint only using the force flag","operationId":"delete_service_endpoint","parameters":[{"name":"endpoint","in":"path","description":"Endpoint identifier","required":true,"schema":{"type":"string"}},{"name":"force","in":"query","description":"If true, the service endpoint will be forcefully deleted. This might break in-flight invocations, use with caution.","style":"simple","schema":{"type":"boolean"}}],"responses":{"202":{"description":"Accepted"},"501":{"description":"Not implemented. Only using the force flag is supported at the moment."},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/services/{service}/methods/{method}":{"get":{"tags":["service_method"],"summary":"Get service method","description":"Get the method of a service","operationId":"get_service_method","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}},{"name":"method","in":"path","description":"Method name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MethodMetadata"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/health":{"get":{"tags":["health"],"summary":"Health check","description":"Check REST API Health.","operationId":"health","responses":{"200":{"description":"OK"}}}},"/services/{service}":{"patch":{"tags":["service"],"summary":"Modify a service","description":"Modify a registered service.","operationId":"modify_service","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModifyServiceRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceMetadata"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/invocations/{invocation_id}":{"delete":{"tags":["invocation"],"summary":"Kill an invocation","description":"Kill the given invocation. When killing, consistency is not guaranteed for service instance state, in-flight invocation to other services, etc. Future releases will support graceful invocation cancellation.","operationId":"cancel_invocation","parameters":[{"name":"invocation_id","in":"path","description":"Invocation identifier.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":""},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/services/{service}/descriptors":{"get":{"tags":["service"],"summary":"List service descriptors","description":"List file descriptors for the service.","operationId":"list_service_descriptors","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/openapi":{"get":{"tags":["openapi"],"summary":"OpenAPI specification","externalDocs":{"url":"https://swagger.io/specification/"},"operationId":"openapi_spec","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}}}}},"/subscriptions/{subscription}":{"delete":{"tags":["subscription"],"summary":"Delete subscription","description":"Delete subscription.","operationId":"delete_subscription","parameters":[{"name":"subscription","in":"path","description":"Subscription identifier","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Accepted"},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/subscriptions":{"get":{"tags":["subscription"],"summary":"List subscriptions","description":"List all subscriptions.","operationId":"list_subscriptions","parameters":[{"name":"sink","in":"query","description":"Filter by the exact specified sink.","style":"simple","schema":{"type":"string"}},{"name":"source","in":"query","description":"Filter by the exact specified source.","style":"simple","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSubscriptionsResponse"}}}}}}},"/services":{"get":{"tags":["service"],"summary":"List services","description":"List all registered services.","operationId":"list_services","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListServicesResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/endpoints":{"post":{"tags":["service_endpoint"],"summary":"Create service endpoint","description":"Create service endpoint. Restate will invoke the endpoint to gather additional information required for registration, such as the services exposed by the service endpoint and their Protobuf descriptor. If the service endpoint is already registered, this method will fail unless `force` is set to `true`.","operationId":"create_service_endpoint","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterServiceEndpointRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterServiceEndpointResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}}},"components":{"schemas":{"ListServiceMethodsResponse":{"type":"object","required":["methods"],"properties":{"methods":{"type":"array","items":{"$ref":"#/components/schemas/MethodMetadata"}}}},"MethodMetadata":{"type":"object","required":["input_type","name","output_type"],"properties":{"name":{"type":"string"},"input_type":{"title":"Input type","description":"Fully qualified message name of the input to the method","type":"string"},"output_type":{"title":"Output type","description":"Fully qualified message name of the output of the method","type":"string"},"key_field_number":{"title":"Key field number","description":"If this is a keyed service, the Protobuf field number of the key within the input type, Otherwise `null`.","type":"integer","format":"uint32","minimum":0.0,"nullable":true}}},"ErrorDescriptionResponse":{"title":"Error description response","description":"Error details of the response","type":"object","required":["message"],"properties":{"message":{"type":"string"},"restate_code":{"title":"Restate code","description":"Restate error code describing this error","type":"string","nullable":true}}},"ModifyServiceRequest":{"type":"object","required":["public"],"properties":{"public":{"title":"Public","description":"If true, the service can be invoked through the ingress. If false, the service can be invoked only from another Restate service.","type":"boolean"}}},"ServiceMetadata":{"type":"object","required":["endpoint_id","instance_type","methods","name","public","revision"],"properties":{"name":{"type":"string"},"methods":{"type":"array","items":{"$ref":"#/components/schemas/MethodMetadata"}},"instance_type":{"$ref":"#/components/schemas/InstanceType"},"endpoint_id":{"title":"Endpoint Id","description":"Endpoint exposing the latest revision of the service.","type":"string"},"revision":{"title":"Revision","description":"Latest revision of the service.","type":"integer","format":"uint32","minimum":0.0},"public":{"title":"Public","description":"If true, the service can be invoked through the ingress. If false, the service can be invoked only from another Restate service.","type":"boolean"}}},"InstanceType":{"type":"string","enum":["Keyed","Unkeyed","Singleton"]},"ListSubscriptionsResponse":{"type":"object","required":["subscriptions"],"properties":{"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/SubscriptionResponse"}}}},"SubscriptionResponse":{"type":"object","required":["id","options","sink","source"],"properties":{"id":{"type":"string"},"source":{"type":"string"},"sink":{"type":"string"},"options":{"type":"object","additionalProperties":{"type":"string"}}}},"ListServicesResponse":{"type":"object","required":["services"],"properties":{"services":{"type":"array","items":{"$ref":"#/components/schemas/ServiceMetadata"}}}},"RegisterServiceEndpointRequest":{"type":"object","required":["uri"],"properties":{"uri":{"title":"Uri","description":"Uri to use to discover/invoke the service endpoint.","type":"string"},"additional_headers":{"title":"Additional headers","description":"Additional headers added to the discover/invoke requests to the service endpoint.","type":"object","additionalProperties":{"type":"string"},"nullable":true},"force":{"title":"Force","description":"If `true`, it will override, if existing, any endpoint using the same `uri`. Beware that this can lead in-flight invocations to an unrecoverable error state.\n\nBy default, this is `true` but it might change in future to `false`.\n\nSee the [versioning documentation](https://docs.restate.dev/services/upgrades-removal) for more information.","default":true,"type":"boolean"}}},"RegisterServiceEndpointResponse":{"type":"object","required":["id","services"],"properties":{"id":{"type":"string"},"services":{"type":"array","items":{"$ref":"#/components/schemas/RegisterServiceResponse"}}}},"RegisterServiceResponse":{"type":"object","required":["name","revision"],"properties":{"name":{"type":"string"},"revision":{"type":"integer","format":"uint32","minimum":0.0}}}}}} +{"openapi":"3.0.0","info":{"title":"Admin API","version":"0.5.0"},"paths":{"/services/{service}/methods":{"get":{"tags":["service_method"],"summary":"List service methods","description":"List all the methods of the given service.","operationId":"list_service_methods","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListServiceMethodsResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/health":{"get":{"tags":["health"],"summary":"Health check","description":"Check REST API Health.","operationId":"health","responses":{"200":{"description":"OK"}}}},"/services/{service}/methods/{method}":{"get":{"tags":["service_method"],"summary":"Get service method","description":"Get the method of a service","operationId":"get_service_method","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}},{"name":"method","in":"path","description":"Method name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MethodMetadata"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/openapi":{"get":{"tags":["openapi"],"summary":"OpenAPI specification","externalDocs":{"url":"https://swagger.io/specification/"},"operationId":"openapi_spec","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}}}}},"/subscriptions":{"get":{"tags":["subscription"],"summary":"List subscriptions","description":"List all subscriptions.","operationId":"list_subscriptions","parameters":[{"name":"sink","in":"query","description":"Filter by the exact specified sink.","style":"simple","schema":{"type":"string"}},{"name":"source","in":"query","description":"Filter by the exact specified source.","style":"simple","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSubscriptionsResponse"}}}}}}},"/services/{service}/descriptors":{"get":{"tags":["service"],"summary":"List service descriptors","description":"List file descriptors for the service.","operationId":"list_service_descriptors","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/services/{service}":{"patch":{"tags":["service"],"summary":"Modify a service","description":"Modify a registered service.","operationId":"modify_service","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModifyServiceRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceMetadata"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/endpoints/{endpoint}":{"delete":{"tags":["service_endpoint"],"summary":"Delete service endpoint","description":"Delete service endpoint. Currently it's supported to remove a service endpoint only using the force flag","operationId":"delete_service_endpoint","parameters":[{"name":"endpoint","in":"path","description":"Endpoint identifier","required":true,"schema":{"type":"string"}},{"name":"force","in":"query","description":"If true, the service endpoint will be forcefully deleted. This might break in-flight invocations, use with caution.","style":"simple","schema":{"type":"boolean"}}],"responses":{"202":{"description":"Accepted"},"501":{"description":"Not implemented. Only using the force flag is supported at the moment."},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/services":{"get":{"tags":["service"],"summary":"List services","description":"List all registered services.","operationId":"list_services","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListServicesResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/invocations/{invocation_id}":{"delete":{"tags":["invocation"],"summary":"Kill an invocation","description":"Kill the given invocation. When killing, consistency is not guaranteed for service instance state, in-flight invocation to other services, etc. Future releases will support graceful invocation cancellation.","operationId":"cancel_invocation","parameters":[{"name":"invocation_id","in":"path","description":"Invocation identifier.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":""},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/subscriptions/{subscription}":{"delete":{"tags":["subscription"],"summary":"Delete subscription","description":"Delete subscription.","operationId":"delete_subscription","parameters":[{"name":"subscription","in":"path","description":"Subscription identifier","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Accepted"},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/endpoints":{"post":{"tags":["service_endpoint"],"summary":"Create service endpoint","description":"Create service endpoint. Restate will invoke the endpoint to gather additional information required for registration, such as the services exposed by the service endpoint and their Protobuf descriptor. If the service endpoint is already registered, this method will fail unless `force` is set to `true`.","operationId":"create_service_endpoint","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterServiceEndpointRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterServiceEndpointResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}}},"components":{"schemas":{"ListServiceMethodsResponse":{"type":"object","required":["methods"],"properties":{"methods":{"type":"array","items":{"$ref":"#/components/schemas/MethodMetadata"}}}},"MethodMetadata":{"type":"object","required":["input_type","name","output_type"],"properties":{"name":{"type":"string"},"input_type":{"title":"Input type","description":"Fully qualified message name of the input to the method","type":"string"},"output_type":{"title":"Output type","description":"Fully qualified message name of the output of the method","type":"string"},"key_field_number":{"title":"Key field number","description":"If this is a keyed service, the Protobuf field number of the key within the input type, Otherwise `null`.","type":"integer","format":"uint32","minimum":0.0,"nullable":true}}},"ErrorDescriptionResponse":{"title":"Error description response","description":"Error details of the response","type":"object","required":["message"],"properties":{"message":{"type":"string"},"restate_code":{"title":"Restate code","description":"Restate error code describing this error","type":"string","nullable":true}}},"ListSubscriptionsResponse":{"type":"object","required":["subscriptions"],"properties":{"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/SubscriptionResponse"}}}},"SubscriptionResponse":{"type":"object","required":["id","options","sink","source"],"properties":{"id":{"type":"string"},"source":{"type":"string"},"sink":{"type":"string"},"options":{"type":"object","additionalProperties":{"type":"string"}}}},"ModifyServiceRequest":{"type":"object","required":["public"],"properties":{"public":{"title":"Public","description":"If true, the service can be invoked through the ingress. If false, the service can be invoked only from another Restate service.","type":"boolean"}}},"ServiceMetadata":{"type":"object","required":["endpoint_id","instance_type","methods","name","public","revision"],"properties":{"name":{"type":"string"},"methods":{"type":"array","items":{"$ref":"#/components/schemas/MethodMetadata"}},"instance_type":{"$ref":"#/components/schemas/InstanceType"},"endpoint_id":{"title":"Endpoint Id","description":"Endpoint exposing the latest revision of the service.","type":"string"},"revision":{"title":"Revision","description":"Latest revision of the service.","type":"integer","format":"uint32","minimum":0.0},"public":{"title":"Public","description":"If true, the service can be invoked through the ingress. If false, the service can be invoked only from another Restate service.","type":"boolean"}}},"InstanceType":{"type":"string","enum":["Keyed","Unkeyed","Singleton"]},"ListServicesResponse":{"type":"object","required":["services"],"properties":{"services":{"type":"array","items":{"$ref":"#/components/schemas/ServiceMetadata"}}}},"RegisterServiceEndpointRequest":{"type":"object","anyOf":[{"type":"object","required":["uri"],"properties":{"uri":{"title":"Uri","description":"Uri to use to discover/invoke the http service endpoint.","type":"string"}}},{"type":"object","required":["arn"],"properties":{"arn":{"title":"ARN","description":"ARN to use to discover/invoke the lambda service endpoint.","type":"string"},"assume_role_arn":{"title":"Assume role ARN","description":"Optional ARN of a role to assume when invoking this endpoint, to support role chaining","type":"string","nullable":true}}}],"properties":{"additional_headers":{"title":"Additional headers","description":"Additional headers added to the discover/invoke requests to the service endpoint.","type":"object","additionalProperties":{"type":"string"},"nullable":true},"force":{"title":"Force","description":"If `true`, it will override, if existing, any endpoint using the same `uri`. Beware that this can lead in-flight invocations to an unrecoverable error state.\n\nBy default, this is `true` but it might change in future to `false`.\n\nSee the [versioning documentation](https://docs.restate.dev/services/upgrades-removal) for more information.","default":true,"type":"boolean"}}},"RegisterServiceEndpointResponse":{"type":"object","required":["id","services"],"properties":{"id":{"type":"string"},"services":{"type":"array","items":{"$ref":"#/components/schemas/RegisterServiceResponse"}}}},"RegisterServiceResponse":{"type":"object","required":["name","revision"],"properties":{"name":{"type":"string"},"revision":{"type":"integer","format":"uint32","minimum":0.0}}}}}} diff --git a/static/schemas/restate.yaml b/static/schemas/restate.yaml index b08a2bff..0d8387f5 100644 --- a/static/schemas/restate.yaml +++ b/static/schemas/restate.yaml @@ -9,7 +9,15 @@ meta: rest_address: 0.0.0.0:9070 rest_concurrency_limit: 1000 storage_path: target/meta/ - proxy_uri: null + service_client: + http: + keep_alive_options: + interval: 40s + timeout: 20s + proxy_uri: null + lambda: + aws_profile: null + assume_role_external_id: null worker: channel_size: 64 timers: @@ -51,12 +59,17 @@ worker: abort_timeout: 1m message_size_warning: 10485760 message_size_limit: null - proxy_uri: null - tmp_dir: /tmp/invoker-018b61e3cf60716699c4f1371df3e19d + tmp_dir: /tmp/invoker-018be36438fd78fcb289c07294b890ca concurrency_limit: null - http2_keep_alive: - interval: 40s - timeout: 20s + service_client: + http: + keep_alive_options: + interval: 40s + timeout: 20s + proxy_uri: null + lambda: + aws_profile: null + assume_role_external_id: null disable_eager_state: false partitions: 1024 tokio_runtime: From 9c628cb5c48a27b1e3415b1687c4181bc774249c Mon Sep 17 00:00:00 2001 From: Jack Kleeman Date: Sat, 18 Nov 2023 17:09:15 +0000 Subject: [PATCH 2/3] Update Lambda docs (#178) * Update Lambda docs Covers new arn and assume role arn discovery functionality * Mount local .aws dir --- docs/restate/managed_service.md | 64 ++++++++++++----- docs/services/deployment/lambda.md | 111 +++++------------------------ 2 files changed, 62 insertions(+), 113 deletions(-) diff --git a/docs/restate/managed_service.md b/docs/restate/managed_service.md index 5105b37d..f3aa352b 100644 --- a/docs/restate/managed_service.md +++ b/docs/restate/managed_service.md @@ -90,24 +90,50 @@ logcli query --no-labels '{kubernetes_container_name="restate"} != `DEBUG` | jso Use `-f` to tail logs - check the [LogCLI docs](https://grafana.com/docs/loki/latest/tools/logcli/#logcli-query-command-reference) for more tips. -## Using the lambda proxy -Managed service users can also request access to the lambda proxy. This is an endpoint that exposes a versioned, authenticated -HTTP url for your Lambda, meaning you don't need to create an API gateway, and you can easily call different versions -of your Lambda with different URLs. Under the hood, the lambda proxy is just a Lambda itself, behind an API gateway, -which accepts the name and version of your Lambda and invokes it. - -By letting us know the AWS account you want to invoke at [info@restate.dev](mailto:info@restate.dev), we will provision -you an API key which is allowed to invoke Lambdas on that AWS account. You'll also need to give the proxy's AWS principal -`arn:aws:iam::663487780041:role/lambda_proxy` access to invoke each of your Lambdas: - -```bash -aws lambda add-permission --function-name --action lambda:InvokeFunction --principal arn:aws:iam::663487780041:role/lambda_proxy --statement-id lambda_proxy +## Giving permission for your cluster to Invoke your Lambdas +Managed service clusters by default assume a role in Restate's AWS account: `arn:aws:iam::663487780041:role/restate-dev`. +However, allowing this role to invoke your Lambda via its resource policy is dangerous and not recommended, as this will +allow *any* managed cluster to invoke your Lambda, not just yours! + +Instead, cross account Lambda access should be achieved with [Role Chaining](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_terms-and-concepts.html). +First, set up a role on one your own AWS accounts that Restate will assume when calling your Lambda. This role needs +a trust policy that allows Restate to assume it: +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::663487780041:role/restate-dev" + }, + "Action": [ + "sts:AssumeRole" + ], + "Condition": { + "StringEquals": { + "sts:ExternalId": [ + "" + ] + } + } + } + ] +} ``` - -You can then discover your Lambda through the proxy like this: -```bash -curl -H "Authorization: Bearer $(cat /token)" https://yourcluster.dev.restate.cloud:8081/endpoints -H 'content-type: application/json' -d \ - '{"uri": "https://.lambda-proxy.restate.cloud///", "additional_headers": {"x-api-key": ""}}' +Notice the trust policy mandates that your cluster name is provided as an +[external ID](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html). This ensures +that your role is being assumed by your Restate cluster, and no one else's. On Restate's side, we ensure that the ID is +always set correctly to the name of cluster. + +This role needs to have permission to call your Lambda. For example, you may want to give it `lambda:InvokeFunction` on `*`, +which will give it access to all Lambdas that allow this role via their +[Resource Policy](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html), which will include +all Lambdas in the same account as the role. For accessing Lambdas in other accounts, you can add the role to their +Resource Policies, or you can create a role per account - Restate can assume a different role per Lambda if needed. + +Once you have a role that has permission to call the Lambda, and allows Restate to assume it, you just need to discover +the Lambda: +```shell +curl -X POST http://:9070/endpoints -H 'content-type: application/json' -d '{"arn": "", "assume_role_arn": "" }' ``` -New Lambdas don't have a version yet, so use `$LATEST` until you've published an immutable version - make sure to escape the `$` in your shell. -See the [versioning documentation](/services/upgrades-removal) for more context. diff --git a/docs/services/deployment/lambda.md b/docs/services/deployment/lambda.md index a79887af..8ac5015b 100644 --- a/docs/services/deployment/lambda.md +++ b/docs/services/deployment/lambda.md @@ -23,14 +23,10 @@ npm run bundle AWS Lambda assumes that the handler can be found under `index.handler` in the uploaded code. By default, this is also the case for the Lambda functions developed with the Restate SDK. -:::caution -Restate assumes that requests come through API Gateway. -So you have to configure API Gateway to sit in front of your Lambda function. -::: ### Managed service If you'd prefer not to manage a runtime instance, we are trialing a managed service that lets you work -with Lambda services without running any infrastructure or even an API gateway. +with Lambda services without running any infrastructure. See [the documentation](/restate/managed_service) for more details. ### Discovery of services @@ -40,22 +36,9 @@ pointed at the Restate runtime and with the Lambda function endpoint as the URI ```shell -curl -X POST http://:9070/endpoints -H 'content-type: application/json' -d '{"uri": "https:///default/"}' -``` - -If your Lambda function requires authentication via an API key, -then you can add this API key to the discovery request to the Restate runtime, as follows: - -```shell -curl -X POST http://:9070/endpoints -H 'content-type: application/json' -d '{"uri": "https:///default/","additional_headers": {"x-api-key": "someapikey"} }' +curl -X POST http://:9070/endpoints -H 'content-type: application/json' -d '{"arn": "arn:aws:lambda:my-region:123456789101:function:my-function:my-version"}' ``` -Here, we added the API key as an additional header to the JSON data of the request. -Replace `someapikey` by your API key. -The Restate runtime will use this API key for all subsequent requests to the Lambda function. - - - ## Tutorial This tutorial shows how to deploy a greeter service written with the Restate Typescript SDK on AWS Lambda. @@ -68,7 +51,7 @@ This tutorial shows how to deploy a greeter service written with the Restate Typ - Latest stable version of [NodeJS](https://nodejs.org/en/) >= v18.17.1 and [npm CLI](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) >= 9.6.7 - [Docker Engine](https://docs.docker.com/engine/install/) or [Podman](https://podman.io/docs/installation) to launch the Restate runtime (not needed for the app implementation itself). - [curl](https://everything.curl.dev/get) -- An AWS account with permissions for Lambda and API Gateway. +- An AWS account with permissions for Lambda. ### Clone the repository @@ -110,20 +93,6 @@ You should now see a function overview with your new function in it. ![Function overview](/img/function-overview.png) -Let's add an API Gateway in front of our function. Click on `Add trigger` below your function in the function overview. -Select API Gateway as a source and fill in the following: - -![API GW configuration](/img/api-gw-config.png) - -If you select `open` for Security, your function will be publicly reachable! -If you select `API key` for Security, then the requests to your API Gateway require an API key in the header. - -Click on `Add` to create the API gateway for your Lambda function. - -You should now see the API gateway appear in the function overview: - -![Overview with API GW](/img/overview-with-apigw.png) - The next step is uploading the zip file with our function code. Open the `Code` tab in the section below the function overview. Click on `Upload from` and select your zip file. @@ -134,51 +103,7 @@ So this means that you should have the Restate Lambda handler assigned to `expor This handler will then be included in `index.js` after creating the zip, and be used by AWS Lambda as the entry point of the Lambda function. To change that you can scroll down to `Runtime settings` and change the handler reference. -One last step before we are in business! -We still need to tell the API Gateway how to forward requests to our Lambda functions and deploy the API. - -Go to your API Gateway by clicking on it in the function overview. -Then click on the name of the API Gateway to get directed to the API Gateway configuration. -You should now be in the `Resources` overview of your API Gateway. - -![API GW Resource](/img/resource-apigw.png) - -In the left-upper corner, click on `Actions` and select `Create resource` from the dropdown. -We configure the resource as a proxy resource, that just forwards all requests to our Lambda function: - -![Create resource](/img/create-resource.png) - -Click on `Create resource`. Now, we need to specify to which Lamda function our requests should be forwarded. - -![Setup proxy](/img/setup-proxy.png) - -Click `Save` and confirm that you want your API Gateway to have permission to invoke your Lambda function. - -You should now see the proxy added to the resources - -![Proxy resource](/img/proxy-resource.png) - -To configure that we want to use an API key for authentication, -click on `Method Request` and set `API Key Required` to `true`. -If you do this, -make sure that you create an API key and link it to your Lambda function and deployment stage via a usage plan. -To do this: -- Go to the API Gateway console and then to `API Keys`. -- Click on the `Actions` dropdown and select `Create API key`. -- Give the API key a name and leave it as an auto generated key. -- After the successful creation, click on `Add to Usage Plan` -- When you deployed your function, a usage plan should have been created. Type in the name of your function and you should see the name of the usage plan pop up with this format: `my-greeter-UsagePlan` -- After linking your usage plan to your API key, click on the usage plan name, to go to the settings of the usage plan. -- If the usage plan has no stage linked to it yet, click on `Add API stage`. Type in the name of the API: `my-greeter-API`. And select a stage. In our case: `default`. Save by clicking on the checkmark. - - -Now let's deploy the API. Go back to the API Gateway overview of our greeter. Click again on `Actions` and select `Deploy API`. -Select `default` as the deployment stage and click on `Deploy`. - -Our API Gateway and Lambda function should now be working! - -You can see the invoke URL by going to `Stages` and then following the path to the endpoint you want to invoke. -For example `default/my-greeter`. The invoke URL will be printed on top of the page. +Our Lambda function should now be working! #### Testing your Lambda function @@ -249,16 +174,24 @@ The body is the base64 encoded string of the response, and stands for `{"value": #### Running the Restate runtime -You don't necessarily need to run the Restate runtime on AWS. -You can also run the Restate runtime locally in a Docker container to test your Lambda function: +You don't necessarily need to run the Restate runtime on AWS, but it does need to be able to obtain credentials to invoke your Lambd, but it does need to be able to obtain credentials to invoke your Lambda. +You can run the Restate runtime locally in a Docker container to test your Lambda function: + +First, get AWS credentials into your environment: +```shell +export AWS_ACCESS_KEY_ID=$(aws --profile default configure get aws_access_key_id) +export AWS_SECRET_ACCESS_KEY=$(aws --profile default configure get aws_secret_access_key) +``` +If you use SSO, the AWS Rust SDK currently requires a minor change to your ~/.aws/config to support this; +see https://github.com/awslabs/aws-sdk-rust/issues/703#issuecomment-1811480196. - On Linux ```shell -docker run --name restate_dev --rm -d --network=host ghcr.io/restatedev/restate-dist:VAR::RESTATE_DIST_VERSION +docker run -e AWS_PROFILE -v ~/.aws/:/root/.aws --name restate_dev --rm -d --network=host ghcr.io/restatedev/restate-dist:VAR::RESTATE_DIST_VERSION ``` - On macOS: ```shell -docker run --name restate_dev --rm -d -p 8080:8080 -p 9070:9070 -p 9071:9071 ghcr.io/restatedev/restate-dist:VAR::RESTATE_DIST_VERSION +docker run -e AWS_PROFILE -v ~/.aws/:/root/.aws --name restate_dev --rm -d -p 8080:8080 -p 9070:9070 -p 9071:9071 ghcr.io/restatedev/restate-dist:VAR::RESTATE_DIST_VERSION ``` Consult the runtime logs via `docker logs restate_dev`. @@ -270,22 +203,12 @@ Stop the runtime (and remove any intermediate state) with `docker stop restate_d Connect to the Restate (e.g. via an SSH session if it is running on EC2) runtime and execute the discovery curl command: ```shell -curl -X POST http://:9070/endpoints -H 'content-type: application/json' -d '{"uri": "https:///default/my-greeter", "additional_headers": {"x-api-key": "your-api-key"} }' +curl -X POST http://:9070/endpoints -H 'content-type: application/json' -d '{"arn": "" }' ``` If you are running the runtime locally, replace `` by `localhost`. If the runtime is running somewhere else, then replace it accordingly. -If you have set up API key authentication for the API Gateway and Lambda, -then you can add the API key as an additional header in the discovery request. -You can find the API key in the API Gateway console by going to `API Keys` and then selecting the key of your function. -After the discovery, the runtime uses this API key for all subsequent requests to the Lambda function. - -If your Lambda function does not require an API key then you can do the discovery without the additional headers: -```shell -curl -X POST http://:9070/endpoints -H 'content-type: application/json' -d '{"uri": "https:///default/my-greeter"}' -``` - When executing this command, you should see the discovered services printed out! ```json From 786be45e41178f6515d75a8dc060cee6c36e5702 Mon Sep 17 00:00:00 2001 From: Jack Kleeman Date: Sat, 18 Nov 2023 17:31:19 +0000 Subject: [PATCH 3/3] Minor rewording of AWS Lambda docs --- docs/services/deployment/lambda.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/docs/services/deployment/lambda.md b/docs/services/deployment/lambda.md index 8ac5015b..64ad1416 100644 --- a/docs/services/deployment/lambda.md +++ b/docs/services/deployment/lambda.md @@ -170,18 +170,13 @@ The response should be the following: The body is the base64 encoded string of the response, and stands for `{"value":"Hello Pete"}`. -### Sending requests your Lambda function +### Sending requests to your Lambda function #### Running the Restate runtime -You don't necessarily need to run the Restate runtime on AWS, but it does need to be able to obtain credentials to invoke your Lambd, but it does need to be able to obtain credentials to invoke your Lambda. -You can run the Restate runtime locally in a Docker container to test your Lambda function: +You don't necessarily need to run the Restate runtime on AWS, but it does need to be able to obtain credentials to invoke your Lambda. +You can run the Restate runtime locally in a Docker container to test your Lambda function, using your local AWS creds (defined in ~/.aws). -First, get AWS credentials into your environment: -```shell -export AWS_ACCESS_KEY_ID=$(aws --profile default configure get aws_access_key_id) -export AWS_SECRET_ACCESS_KEY=$(aws --profile default configure get aws_secret_access_key) -``` If you use SSO, the AWS Rust SDK currently requires a minor change to your ~/.aws/config to support this; see https://github.com/awslabs/aws-sdk-rust/issues/703#issuecomment-1811480196.