feat: add logs propagation from server to client
diff --git a/go.mod b/go.mod
index 6babf99..fd25ffc 100644
--- a/go.mod
+++ b/go.mod
@@ -13,6 +13,6 @@
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- golang.org/x/sys v0.25.0 // indirect
+ golang.org/x/sys v0.26.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 83fa656..43a9a28 100644
--- a/go.sum
+++ b/go.sum
@@ -21,8 +21,8 @@
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
-golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/openwhisk/forward_proxy.go b/openwhisk/forward_proxy.go
index 1b6b991..08927e3 100644
--- a/openwhisk/forward_proxy.go
+++ b/openwhisk/forward_proxy.go
@@ -74,6 +74,31 @@
sendError(w, http.StatusBadGateway, "Error forwarding run request. Check logs for details.")
}
+ proxy.ModifyResponse = func(response *http.Response) error {
+ if response.StatusCode == http.StatusOK {
+ // Decode the response
+ var remoteReponse RemoteRunResponse
+ err := json.NewDecoder(response.Body).Decode(&remoteReponse)
+ if err != nil {
+ Debug("Error decoding remote response: %v", err)
+ return err
+ }
+
+ // Write the logs to the client logs
+ if _, err := ap.outFile.WriteString(remoteReponse.Out); err != nil {
+ Debug("Error writing remote response out to client: %v", err)
+ }
+ if _, err := ap.errFile.WriteString(remoteReponse.Err); err != nil {
+ Debug("Error writing remote response err to client: %v", err)
+ }
+
+ // Keep the response body only
+ response.Body = io.NopCloser(bytes.NewReader(remoteReponse.Response))
+ }
+
+ return nil
+ }
+
Debug("Forwarding run request with id %s to %s", newBody.ProxiedActionID, ap.clientProxyData.ProxyURL.String())
proxy.ServeHTTP(w, r)
if f, ok := w.(http.Flusher); ok {
diff --git a/openwhisk/forward_proxy_test.go b/openwhisk/forward_proxy_test.go
index 45e98c3..42a3a60 100644
--- a/openwhisk/forward_proxy_test.go
+++ b/openwhisk/forward_proxy_test.go
@@ -65,13 +65,13 @@
}
func Example_forwardRunRequest() {
+ clientLog, _ := os.CreateTemp("", "log")
// create a client ActionProxy
- clientAP := NewActionProxy("", "", nil, nil, ProxyModeClient)
+ clientAP := NewActionProxy("", "", clientLog, clientLog, ProxyModeClient)
// create a server ActionProxy
compiler, _ := filepath.Abs("common/gobuild.py")
- log, _ := os.CreateTemp("", "log")
- serverAP := NewActionProxy("./action", compiler, log, log, ProxyModeServer)
+ serverAP := NewActionProxy("./action", compiler, nil, nil, ProxyModeServer)
// start the server
ts := httptest.NewServer(serverAP)
@@ -95,8 +95,9 @@
// wait 2 seconds before declaring a test done
time.Sleep(2 * time.Second)
ts.Close()
- dump(log)
+ dump(clientLog)
fmt.Println(runW.Body.String())
+ os.Remove(clientLog.Name())
// Output:
// {"ok":true}
diff --git a/openwhisk/initHandler.go b/openwhisk/initHandler.go
index c937b47..0ad8cc7 100644
--- a/openwhisk/initHandler.go
+++ b/openwhisk/initHandler.go
@@ -70,7 +70,15 @@
ap.serverProxyData = &ServerProxyData{actions: make(map[string]*ActionProxy)}
}
- innerActionProxy := NewActionProxy(ap.baseDir, ap.compiler, ap.outFile, ap.errFile, ProxyModeNone)
+ outLog, err := os.CreateTemp("", "out-log")
+ if err != nil {
+ outLog = ap.outFile
+ }
+ errLog, err := os.CreateTemp("", "err-log")
+ if err != nil {
+ errLog = ap.errFile
+ }
+ innerActionProxy := NewActionProxy(ap.baseDir, ap.compiler, outLog, errLog, ProxyModeNone)
id, err := innerActionProxy.doInit(r, w)
if err != nil {
return
diff --git a/openwhisk/runHandler.go b/openwhisk/runHandler.go
index a32b58d..621af69 100644
--- a/openwhisk/runHandler.go
+++ b/openwhisk/runHandler.go
@@ -23,6 +23,7 @@
"fmt"
"io"
"net/http"
+ "os"
)
type runRequest struct {
@@ -35,6 +36,12 @@
Error string `json:"error"`
}
+type RemoteRunResponse struct {
+ Response json.RawMessage `json:"response"`
+ Out string `json:"out"`
+ Err string `json:"err"`
+}
+
func sendError(w http.ResponseWriter, code int, cause string) {
errResponse := ErrResponse{Error: cause}
b, err := json.Marshal(errResponse)
@@ -71,20 +78,21 @@
}
actionID := runRequest.ProxiedActionID
-
innerActionProxy, ok := ap.serverProxyData.actions[actionID]
if !ok {
Debug("Action %s not found in server proxy data", actionID)
sendError(w, http.StatusNotFound, "Action not found in remote runtime. Check logs for details.")
}
- innerActionProxy.doProxiedRun(w, &runRequest)
+ innerActionProxy.doServerModeRun(w, &runRequest)
+
return
}
ap.doRun(w, r)
}
-func (ap *ActionProxy) doProxiedRun(w http.ResponseWriter, bodyRequest *runRequest) {
+
+func (ap *ActionProxy) doServerModeRun(w http.ResponseWriter, bodyRequest *runRequest) {
var bodyBuf bytes.Buffer
err := json.NewEncoder(&bodyBuf).Encode(bodyRequest)
if err != nil {
@@ -93,7 +101,87 @@
}
body := bytes.Replace(bodyBuf.Bytes(), []byte("\n"), []byte(""), -1)
- ap.executeAction(w, body)
+ // check if you have an action
+ if ap.theExecutor == nil {
+ sendError(w, http.StatusInternalServerError, "no action defined yet")
+ return
+ }
+
+ // check if the process exited
+ if ap.theExecutor.Exited() {
+ sendError(w, http.StatusInternalServerError, "command exited")
+ return
+ }
+
+ // execute the action
+ response, err := ap.theExecutor.Interact(body)
+
+ // check for early termination
+ if err != nil {
+ Debug("WARNING! Command exited")
+ ap.theExecutor = nil
+ sendError(w, http.StatusBadRequest, "command exited")
+ return
+ }
+ DebugLimit("received:", response, 120)
+
+ // check if the answer is an object map or array
+ var objmap map[string]*json.RawMessage
+ var objarray []interface{}
+ err = json.Unmarshal(response, &objmap)
+ if err != nil {
+ err = json.Unmarshal(response, &objarray)
+ if err != nil {
+ sendError(w, http.StatusBadGateway, "The action did not return a dictionary or array.")
+ return
+ }
+ }
+
+ // Get the stdout and stderr from the executor
+ outStr, err := os.ReadFile(ap.outFile.Name())
+ if err != nil {
+ outStr = []byte(fmt.Sprintf("Error reading stdout: %v", err))
+ }
+
+ errStr, err := os.ReadFile(ap.errFile.Name())
+ if err != nil {
+ errStr = []byte(fmt.Sprintf("Error reading stderr: %v", err))
+ }
+
+ // create the response
+ remoteResponse := RemoteRunResponse{
+ Response: response,
+ Out: string(outStr),
+ Err: string(errStr),
+ }
+
+ // turn response struct into json
+ responsePayload, err := json.Marshal(remoteResponse)
+ if err != nil {
+ sendError(w, http.StatusInternalServerError, fmt.Sprintf("Error marshalling response: %v", err))
+ return
+ }
+
+ // write response
+ w.Header().Set("Content-Type", "application/json")
+ w.Header().Set("Content-Length", fmt.Sprintf("%d", len(responsePayload)))
+ numBytesWritten, err := w.Write(responsePayload)
+
+ // flush output if possible
+ if f, ok := w.(http.Flusher); ok {
+ f.Flush()
+ }
+
+ // handle writing errors
+ if err != nil {
+ sendError(w, http.StatusInternalServerError, fmt.Sprintf("Error writing response: %v", err))
+ return
+ }
+ if numBytesWritten != len(response) {
+ sendError(w, http.StatusInternalServerError, fmt.Sprintf("Only wrote %d of %d bytes to response", numBytesWritten, len(response)))
+ return
+ }
+
}
func (ap *ActionProxy) doRun(w http.ResponseWriter, r *http.Request) {
@@ -107,10 +195,6 @@
body = bytes.Replace(body, []byte("\n"), []byte(""), -1)
- ap.executeAction(w, body)
-}
-
-func (ap *ActionProxy) executeAction(w http.ResponseWriter, body []byte) {
// check if you have an action
if ap.theExecutor == nil {
sendError(w, http.StatusInternalServerError, "no action defined yet")
diff --git a/openwhisk/version.go b/openwhisk/version.go
index 81e6f1c..191b162 100644
--- a/openwhisk/version.go
+++ b/openwhisk/version.go
@@ -17,4 +17,4 @@
package openwhisk
// Version number - internal
-var Version = "1.18.1"
+var Version = "1.18.2"