From 14c152b1b1405615ab350e8113761efc60bf0448 Mon Sep 17 00:00:00 2001 From: encalada Date: Tue, 5 Dec 2023 17:51:15 +0100 Subject: [PATCH] From PR review Addressing all good feedback, to inline with other CE samples --- grpc/Dockerfile.client | 4 +-- grpc/Dockerfile.server | 4 +-- grpc/README.md | 2 +- grpc/client/main.go | 39 +++++++++++++----------- grpc/regenerate-grpc-code.sh | 25 ++++++++++++++++ grpc/{run.sh => run} | 34 +++++++++++++++++++-- grpc/server/main.go | 58 ++++++++++++++++++++++++++++++------ 7 files changed, 131 insertions(+), 35 deletions(-) create mode 100755 grpc/regenerate-grpc-code.sh rename grpc/{run.sh => run} (53%) diff --git a/grpc/Dockerfile.client b/grpc/Dockerfile.client index 3b672657..f2a31868 100644 --- a/grpc/Dockerfile.client +++ b/grpc/Dockerfile.client @@ -1,4 +1,4 @@ -FROM golang:latest AS stage +FROM icr.io/codeengine/golang:latest AS stage WORKDIR /app/src @@ -12,7 +12,7 @@ COPY go.sum . RUN CGO_ENABLED=0 GOOS=linux go build -o client ./client -FROM golang:latest +FROM icr.io/codeengine/golang:latest WORKDIR /app/src diff --git a/grpc/Dockerfile.server b/grpc/Dockerfile.server index f53344e2..a38693c3 100644 --- a/grpc/Dockerfile.server +++ b/grpc/Dockerfile.server @@ -1,4 +1,4 @@ -FROM golang:latest AS stage +FROM icr.io/codeengine/golang:latest AS stage WORKDIR /app/src @@ -12,7 +12,7 @@ COPY go.sum . RUN CGO_ENABLED=0 GOOS=linux go build -o server ./server -FROM golang:latest +FROM icr.io/codeengine/golang:latest WORKDIR /app/src diff --git a/grpc/README.md b/grpc/README.md index 83fb2e50..929d538d 100644 --- a/grpc/README.md +++ b/grpc/README.md @@ -30,7 +30,7 @@ You can try to deploy this microservice architecture in your Code Engine project Once you have selected your Code Engine project, you only need to run: ```sh -./run.sh +./run ``` ## Todo: diff --git a/grpc/client/main.go b/grpc/client/main.go index b6775a57..47389403 100644 --- a/grpc/client/main.go +++ b/grpc/client/main.go @@ -33,6 +33,12 @@ func BuyGroceryHandler(groceryClient ec.GroceryClient) func(w http.ResponseWrite } } +func Fail(w http.ResponseWriter, msg string, err error) { + fmt.Printf("%s: %v\n", msg, err) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(fmt.Sprintf("%s\n", msg))) +} + func GetHandler(w http.ResponseWriter, r *http.Request, groceryClient ec.GroceryClient) { // Contact the server and print out its response. @@ -49,17 +55,12 @@ func GetHandler(w http.ResponseWriter, r *http.Request, groceryClient ec.Grocery itemList, err := groceryClient.ListGrocery(ctx, &category) if err != nil { - fmt.Printf("failed to list grocery: %v", err) - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("failed to list grocery")) + Fail(w, "failed to list groceries", err) return } - for _, item := range itemList.Item { - json.NewEncoder(w).Encode(fmt.Sprintf("Item Name: %v\n", item.Name)) - } - - w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(itemList.Item) } func BuyHandler(w http.ResponseWriter, r *http.Request, groceryClient ec.GroceryClient) { @@ -71,7 +72,7 @@ func BuyHandler(w http.ResponseWriter, r *http.Request, groceryClient ec.Grocery amount, err := strconv.ParseFloat(pAmount, 64) if err != nil { - http.Error(w, "invalid amount parameter", http.StatusBadRequest) + Fail(w, "invalid amount parameter", err) } category := ec.Category{ @@ -81,26 +82,28 @@ func BuyHandler(w http.ResponseWriter, r *http.Request, groceryClient ec.Grocery item, err := groceryClient.GetGrocery(context.Background(), &category) if err != nil { - w.WriteHeader(http.StatusBadRequest) + Fail(w, fmt.Sprintf("failed to get grocery item by name: %v", category.Itemname), err) + return } - json.NewEncoder(w).Encode(fmt.Sprintf("Grocery: \n Item: %v \n Quantity: %v\n", item.GetName(), item.GetQuantity())) - paymentRequest := ec.PaymentRequest{ Amount: amount, Item: item, } paymentResponse, _ := groceryClient.MakePayment(context.Background(), &paymentRequest) - json.NewEncoder(w).Encode(fmt.Sprintf("Payment Response: \n Purchase: %v \n Details: %v \n State: %v \n Change: %v\n", paymentResponse.PurchasedItem, paymentResponse.Details, paymentResponse.Success, paymentResponse.Change)) - w.WriteHeader(http.StatusOK) -} -func main() { + // if !paymentResponse.Success { + // Fail(w, "failed to buy grocery, not enough money", errors.New("not enough money")) + // return + // } - serverLocalEndpoint := "LOCAL_ENDPOINT_WITH_PORT" + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(paymentResponse) +} - localEndpoint := os.Getenv(serverLocalEndpoint) +func main() { + localEndpoint := os.Getenv("LOCAL_ENDPOINT_WITH_PORT") fmt.Printf("using local endpoint: %s\n", localEndpoint) conn, err := grpc.Dial(localEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) diff --git a/grpc/regenerate-grpc-code.sh b/grpc/regenerate-grpc-code.sh new file mode 100755 index 00000000..8661e72f --- /dev/null +++ b/grpc/regenerate-grpc-code.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -euo pipefail + +BASEDIR="$(dirname "$0")" + +if ! hash protoc >/dev/null 2>&1; then + echo "[ERROR] protoc compiler plugin is not installed, bailing out." + echo "[INFO] refer to https://grpc.io/docs/languages/go/quickstart/#prerequisites for instalation guidelines." + echo + exit 1 +fi + +if ! hash protoc-gen-go >/dev/null 2>&1; then + echo "[ERROR] protoc-gen-go plugin is not installed, bailing out." + echo "[INFO] refer to https://grpc.io/docs/languages/go/quickstart/#prerequisites for instalation guidelines." + echo "[INFO] ensure your GOPATH is in your PATH". + exit 1 +fi + + +echo "[INFO] Recompiling .proto file, this will regenerate the *.pb.go files." +protoc --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + ${BASEDIR}/ecommerce/ecommerce.proto diff --git a/grpc/run.sh b/grpc/run similarity index 53% rename from grpc/run.sh rename to grpc/run index d37c725d..6b7813fc 100755 --- a/grpc/run.sh +++ b/grpc/run @@ -5,6 +5,21 @@ set -euo pipefail SERVER_APP_NAME="a-grpc-server" CLIENT_APP_NAME="a-grpc-client" +# Clean up previous run +function clean() { + echo "[INFO] Deleting CE client/server application ${CLIENT_APP_NAME}" + ibmcloud ce app delete --name $CLIENT_APP_NAME --force --ignore-not-found > /dev/null 2>&1 + echo "[INFO] Deleting CE gRPC server application ${SERVER_APP_NAME}" + ibmcloud ce app delete --name $SERVER_APP_NAME --force --ignore-not-found > /dev/null 2>&1 +} + +if [ $# -ge 1 ] && [ -n "$1" ] +then + echo "[INFO] going to clean existing project resources" + clean + exit 0 +fi + # Create the gRPC server app echo "[INFO] Creating CE gRPC server application ${SERVER_APP_NAME}" ibmcloud ce app create --name "${SERVER_APP_NAME}" --port h2c:8080 --min-scale 1 --build-source . --build-dockerfile Dockerfile.server @@ -26,6 +41,19 @@ echo "[INFO] Endpoint is: ${URL}" echo "[INFO] Retrieving available electronic items" curl -q "${URL}"/listgroceries/electronics -# Buy an item from electronics and pay with 2000 -echo "[INFO] Going to buy an iphone" -curl -q "${URL}"/buygrocery/electronics/iphone/2000.0 \ No newline at end of file +# Buy an item from food and pay with 5.0 dollars +echo "[INFO] Going to buy an apple, paying with 5.0 dollars" +JSON_REPONSE=$(curl -s "${URL}"/buygrocery/vegetables/apple/5.0 | jq '.success') +echo $JSON_REPONSE | jq . + +# Validate payment operation +echo "[INFO] Validating payment operation state" +OPERATION_SUCCEEDED=$(echo $JSON_REPONSE | jq '.success') +if [ "${OPERATION_SUCCEEDED}" == "null" ] +then + echo "[ERROR] Payment failed, bailing out." + exit 1 +else + echo "[INFO] Successful payment operation." + exit 0 +fi \ No newline at end of file diff --git a/grpc/server/main.go b/grpc/server/main.go index 761b0b29..472328cf 100644 --- a/grpc/server/main.go +++ b/grpc/server/main.go @@ -27,16 +27,46 @@ func initGroceryServer() *GroceryServer { return &GroceryServer{ Products: []Product{ { - Category: "vegetables", + Category: "food", Name: "carrot", - Quantity: "4", + Quantity: "1", Price: 0.5, }, { - Category: "fruit", + Category: "food", Name: "apple", - Quantity: "3", - Price: 0.6, + Quantity: "1", + Price: 0.5, + }, + { + Category: "food", + Name: "banana", + Quantity: "1", + Price: 0.5, + }, + { + Category: "food", + Name: "lemon", + Quantity: "1", + Price: 0.5, + }, + { + Category: "clothes", + Name: "tshirt", + Quantity: "1", + Price: 25.0, + }, + { + Category: "clothes", + Name: "pants", + Quantity: "1", + Price: 65.0, + }, + { + Category: "clothes", + Name: "socks", + Quantity: "1", + Price: 5.0, }, { Category: "electronics", @@ -44,6 +74,12 @@ func initGroceryServer() *GroceryServer { Quantity: "1", Price: 1200.99, }, + { + Category: "electronics", + Name: "keyboard", + Quantity: "1", + Price: 100.99, + }, { Category: "electronics", Name: "usb", @@ -68,7 +104,7 @@ func (gs *GroceryServer) GetGrocery(ctx context.Context, in *ec.Category) (*ec.I } } - return &ec.Item{}, nil + return &ec.Item{}, errors.New("category item not found") } func (gs *GroceryServer) ListGrocery(ctx context.Context, in *ec.Category) (*ec.ItemList, error) { @@ -95,14 +131,18 @@ func (gs *GroceryServer) MakePayment(ctx context.Context, in *ec.PaymentRequest) transactionSuccessfull := true var transactionDetails string + change := amount - purchasedItem.Price + + if change < 0 { + transactionSuccessfull = false + } + if transactionSuccessfull { transactionDetails = fmt.Sprintf("Transaction of amount %v is successful", amount) } else { - transactionDetails = fmt.Sprintf("Transaction of amount %v failed", amount) + transactionDetails = fmt.Sprintf("Transaction of amount %v failed, payment missmatch", amount) } - change := amount - purchasedItem.Price - return &ec.PaymentResponse{ Success: transactionSuccessfull, PurchasedItem: purchasedItem,