Is it Possible to have Multiple lambda functions in single Go binary? - go

I am experimenting with Go on AWS lambda, and i found that each function requires a binary to be uploaded for execution.
My question is that, is it possible to have a single binary that can have two different Handler functions, which can be loaded by two different lambda functions.
for example
func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
fmt.Println("Received body in Handler 1: ", request.Body)
return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
}
func Handler1(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
fmt.Println("Received body in Handler 2: ", request.Body)
return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
}
func EndPoint1() {
lambda.Start(Handler)
}
func EndPoint2() {
lambda.Start(Handler1)
}
and calling the EndPoints in main in such a way that it registers both the EndPoints and the same binary would be uploaded to both the functions MyFunction1 and MyFunction2.
I understand that having two different binary is good because it reduces the load/size of each function.
But this is just an experimentation.
Thanks in advance :)

I believe it is not possible since Lambda console says the handler is the name of the executable file:
Handler: The executable file name value. For example, "myHandler" would call the main function in the package “main” of the myHandler executable program.
So one single executable file is unable to host two different handlers.

you can do the workaround to have same executable to upload to two different lambda like following
func main() {
switch cfg.LambdaCommand {
case "select_lambda_1":
lambda1handler()
case "select_lambda_2":
lambda2handler()

I've had success by adding an environment variable to the functions in the template.yaml, and making the main() check the variable and call the appropriate handler.

Related

How to extract path from user request in golang grpc-gateway

i have a question. Is it possible to extract via metadata path from user request.
Here i have my proto file with defined method.
rpc AllPath(google.protobuf.Empty) returns (google.protobuf.Empty) {
option (google.api.http) = {
get: "/*",
};
}
rpc Auth(google.protobuf.Empty) returns (TokenRender) {
option (google.api.http) = {
get: "/auth"
};
}
}
In AllPath function in my server file im using something like this, found on grpc-gateway ecosystem website.
path := make(map[string]string)
if pattern, ok := runtime.HTTPPathPattern(ctx); ok {
path["pattern"] = pattern // /v1/example/login
}
fmt.Printf("Current path is: %v", path["pattern"])
but my current pattern/path is like i defined in proto file: Current path is: /*
If anyone have idea how to deal with this thing i would appreciate it :)
Best, Kacper
gRPC-Gateway passes various bits of information from the originating HTTP request via gRPC metadata. I don't believe the raw path is supplied, however. It is still possible to get the path passed through by registering a metadata annotator.
When calling github.com/grpc-ecosystem/grpc-gateway/v2/runtime.NewServeMux(), leverage the WithMetadata option func:
mux := runtime.NewServeMux(runtime.WithMetadata(func(_ context.Context, req *http.Request) metadata.MD {
return metadata.New(map[string]string{
"grpcgateway-http-path": req.URL.Path,
})
}))
Then in your gRPC service implementation, you can retrieve the value via the incoming context:
func (s *server) AllPath(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
md, _ := metadata.FromIncomingContext(ctx)
log.Printf("path: %s", md["grpcgateway-http-path"][0])
return &emptypb.Empty{}, nil
}
When hitting, e.g. /foo, this should log:
2022/10/25 15:31:42 path: /foo

Why Google Logging client libraries not logging inside Google cloud functions?

I'm trying to implement a google cloud function to test Google Logging client library. below is my code
// Package p contains an HTTP Cloud Function.
package loggingclient
import (
"cloud.google.com/go/logging"
"net/http"
"context"
"fmt"
)
// HelloWorld prints the JSON encoded "message" field in the body
// of the request or "Hello, World!" if there isn't one.
func HelloWorld(w http.ResponseWriter, r *http.Request) {
label := map[string]string{"priority": "High"}
var projectName = "my-project-id"
ctx := context.Background()
client, err := logging.NewClient(ctx, projectName)
if err != nil {
fmt.Printf("client not created: %v", err)
}
lg := client.Logger("MY-LOGGER")
lg.Log(logging.Entry{
Payload: "Hello, This is error!!",
Severity: logging.Error,
Labels: label,
})
client.Close()
}
Here, I'm expecting a log entry with a message:"Hello, This is error!!" and with a lable:"priority": "High" and severirty "ERROR"
But actually, when I trigger this Cloud Function, I didn't get any new log entries. Therefore don't client logging libraries work inside cloud functions?, How to resolve this?
Thanks
It works on cloud functions. I have done the exact same thing in a cloud function before. You can use google's official documenation with cloud function logging here
Also ensure that the service account have necessary permissions for logging
https://cloud.google.com/logging/docs/access-control

can a lambda invoke another lambda, and then exit before child lambda completes?

I have two AWS lambdas written in go. One lambda invokes the other like this:
payload, err := json.Marshal(request)
if err != nil {
log.Printf("ERROR: could not marshal request [%v] into model.ChildLambdaRequest - %v\n", request, err)
return false
}
log.Printf("--- debug sending payload: %s", payload)
// Invoke Child
result, err := client.Invoke(&lambda.InvokeInput{
FunctionName: aws.String(lambdaName),
Payload: payload,
})
if err != nil {
log.Printf("ERROR: could not invoke Lambda function client [%v] - %v\n", lambdaName, err)
return false
}
The child lambda completes like this:
return model.EventResponse{Success: true}, nil
I know this is doing two things: 1.) it's finishing the execution and 2.) it's returning a value. Is there any way to separate these two actions, so that I can return a value at the top, but complete execution at a later time?
Yes, you need to pass InvocationType: "Event" to the Invoke call, documented here. The default invocation type is RequestResponse which waits for the response from the invoked Lambda.
It's possible with Destinations in Lambda.
Although I would check out AWS Step Functions, which gives you this functionality together with built-in retries and error handling.
It works with existing Lambda functions and has a pretty easy to use interface.
Last option that could work, you send a message from the first function to an SQS Queue,
and your second lambda is triggered by the SQS Queue. This also gives you retry capabilities.

Unable to test a Golang CLI tool's output

I have a cli tool written in Go which produces the following output:
Command: config get
Env: int
Component: foo-component
Unable to find any configuration within Cosmos (http://api.foo.com) for foo-component.
I would like to verify this output within a test.
The test I have written (and doesn't pass) is as follows:
package command
import (
"testing"
"github.com/my/package/foo"
)
type FakeCliContext struct{}
func (s FakeCliContext) String(name string) string {
return "foobar"
}
func ExampleInvalidComponentReturnsError() {
fakeBaseURL := "http://api.foo.com"
fakeCliContext := &FakeCliContext{}
fakeFetchFlag := func(foo.CliContext) (map[string]string, error) {
return map[string]string{
"env": "int",
"component": "foo-component",
}, nil
}
GetConfig(*fakeCliContext, fakeFetchFlag, fakeBaseURL)
// Output:
// Command: config get
// Env: int
// Component: foo-component
//
// Unable to find any configuration within Cosmos (http://api.foo.com) for foo-component.
}
The majority of the code is creating fake objects that I'm injecting into my function call GetConfig.
Effectively there is no return value from GetConfig only a side effect of text being printed to stdout.
So I'm using the Example<NameOfTest> format to try and verify the output.
But all I just back when I run go test -v is:
=== RUN ExampleInvalidComponentReturnsError
exit status 1
FAIL github.com/my/package/thing 0.418s
Does anyone know what I might be missing?
I've found that if I add an additional test after the 'Example' one above, for example called Test<NameOfTest> (but consistenting of effectively the same code), then this will actually display the function's output into my stdout when running the test:
func TestInvalidComponentReturnsError(t *testing.T) {
fakeBaseURL := "http://api.foo.com"
fakeCliContext := &FakeCliContext{}
fakeFetchFlag := func(utils.CliContext) (map[string]string, error) {
return map[string]string{
"env": "int",
"component": "foo-component",
}, nil
}
GetConfig(*fakeCliContext, fakeFetchFlag, fakeBaseURL)
}
The above example test will now show the following output when executing go test -v:
=== RUN TestInvalidComponentReturnsError
Command: config get
Env: int
Component: foo-component
Unable to find any configuration within Cosmos (http://api.foo.com) for foo-component.
exit status 1
FAIL github.com/bbc/apollo/command 0.938s
OK so the solution to this problem was part architecture and part removal/refactor of code.
I extracted the private functions from the cli command package so they became public functions in a separate function
I refactored the code so that all dependencies were injected, this then allowed me to mock these objects and verify the the expected methods were called
Now the private functions are in a package and made public, I'm able to test those things specifically, outside of the cli context
Finally, I removed the use of os.Exit as that was a nightmare to deal with and wasn't really necessary

Selectively Follow Redirects in Go

I'm trying to write a twitter reader that resolves the final URLs of link shorteners etc, but gives me a URL along the way for a list of manually defined host patterns. The reason to do this is that i don't want to end up with the paywall URL but the one before.
As far as i can tell the way to do this is write my own client based on the default RoundTripper because returning an error from a custom CheckRedirect function aborts the client without yielding a response.
Is there a way to use the default client and record a list of URLs/specific URL from a custom checkRedirect function?
The client request will actually still return the last valid Response in cases where your custom CheckResponse yields an error (As mentioned in the comments).
http://golang.org/pkg/net/http/#Client
If CheckRedirect returns an error, the Client's Get method returns both the previous Response and CheckRedirect's error (wrapped in a url.Error) instead of issuing the Request req.
If you maintain a list of "known" paywall-urls, you can abort the paywall-redirect in your CheckResponse with a custom error type (Paywalled in the example below).
Your error handling code later has to consider that error type as a special (non-erroneous) case.
Example:
package main
import (
"errors"
"fmt"
"net/http"
"net/url"
)
var Paywalled = errors.New("next redirect would hit a paywall")
var badHosts = map[string]error{
"registration.ft.com": Paywalled,
}
var client = &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// N.B.: when used in production, also check for redirect loops
return badHosts[req.URL.Host]
},
}
func main() {
resp, err := client.Get("http://on.ft.com/14pQBYE")
// ignore non-nil err if it's a `Paywalled` wrapped in url.Error
if e, ok := err.(*url.Error); (ok && e.Err != Paywalled) || (!ok && err != nil) {
fmt.Println("error: ", err)
return
}
resp.Body.Close()
fmt.Println(resp.Request.URL)
}

Resources