Skip to content

Commit 4f81de9

Browse files
committed
Remove leaking log from deploy
This commit removing lekaing logs from deploy API in SDK. It also returns typed errors from API resoponse and allows as to check for some custom errors which functions like `IsNotFound` or `IsUnauthorized`. This also updates DeployFuntion API to return a typed response and `http.Response`. Signed-off-by: Vivek Singh <[email protected]>
1 parent 38f6230 commit 4f81de9

10 files changed

+421
-107
lines changed

commands/deploy.go

+16-5
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,15 @@ Error: %s`, fprocessErr.Error())
276276
if msg := checkTLSInsecure(services.Provider.GatewayURL, deploySpec.TLSInsecure); len(msg) > 0 {
277277
fmt.Println(msg)
278278
}
279-
statusCode := proxyClient.DeployFunction(ctx, deploySpec)
280-
if badStatusCode(statusCode) {
281-
failedStatusCodes[k] = statusCode
279+
dRes, res, err := proxyClient.DeployFunction(ctx, deploySpec)
280+
281+
if err == nil {
282+
fmt.Println(dRes.Message)
283+
fmt.Printf("URL: %s\n\n", dRes.URL)
284+
}
285+
286+
if badStatusCode(res.StatusCode) {
287+
failedStatusCodes[k] = res.StatusCode
282288
}
283289
}
284290
} else {
@@ -375,9 +381,14 @@ func deployImage(
375381
fmt.Println(msg)
376382
}
377383

378-
statusCode = client.DeployFunction(ctx, deploySpec)
384+
dRes, res, err := client.DeployFunction(ctx, deploySpec)
385+
if err != nil {
386+
return res.StatusCode, err
387+
}
379388

380-
return statusCode, nil
389+
fmt.Println(dRes.Message)
390+
fmt.Printf("URL: %s\n\n", dRes.URL)
391+
return res.StatusCode, nil
381392
}
382393

383394
func mergeSlice(values []string, overlay []string) []string {

commands/describe.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ func runDescribe(cmd *cobra.Command, args []string) error {
8282
}
8383

8484
//To get correct value for invocation count from /system/functions endpoint
85-
functionList, err := cliClient.ListFunctions(ctx, functionNamespace)
85+
functionList, _, err := cliClient.ListFunctions(ctx, functionNamespace)
8686
if err != nil {
87-
return err
87+
return actionableErrorMessage(err)
8888
}
8989

9090
var invocationCount int

commands/errors.go

+13
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
package commands
55

66
import (
7+
"fmt"
78
"strings"
9+
10+
"github.com/openfaas/faas-cli/proxy"
811
)
912

1013
const (
@@ -24,3 +27,13 @@ func checkTLSInsecure(gateway string, tlsInsecure bool) string {
2427
}
2528
return ""
2629
}
30+
31+
//actionableErrorMessage print actionable error message based on APIError check
32+
func actionableErrorMessage(err error) error {
33+
if proxy.IsUnknown(err) {
34+
return fmt.Errorf("server returned unexpected status response: %s", err.Error())
35+
} else if proxy.IsUnauthorized(err) {
36+
return fmt.Errorf("unauthorized access, run \"faas-cli login\" to setup authentication for this server")
37+
}
38+
return err
39+
}

commands/list.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ func runList(cmd *cobra.Command, args []string) error {
7373
return err
7474
}
7575

76-
functions, err := proxyClient.ListFunctions(context.Background(), functionNamespace)
76+
functions, _, err := proxyClient.ListFunctions(context.Background(), functionNamespace)
7777
if err != nil {
78-
return err
78+
return actionableErrorMessage(err)
7979
}
8080

8181
if sortOrder == "name" {

proxy/client.go

+30
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package proxy
22

33
import (
4+
"bytes"
45
"context"
6+
"encoding/json"
57
"fmt"
68
"io"
9+
"io/ioutil"
710
"net/http"
811
"net/http/httputil"
912
"net/url"
@@ -129,3 +132,30 @@ func addQueryParams(u string, params map[string]string) (string, error) {
129132
func (c *Client) AddCheckRedirect(checkRedirect func(*http.Request, []*http.Request) error) {
130133
c.httpClient.CheckRedirect = checkRedirect
131134
}
135+
136+
// parseResponse function parses HTTP response body into a typed struct
137+
// or copies to io.Writer object. If it fails to decode response body
138+
// then it re-construct it so that it can be read later
139+
func parseResponse(res *http.Response, v interface{}) error {
140+
var err error
141+
defer res.Body.Close()
142+
143+
switch v := v.(type) {
144+
case nil:
145+
case io.Writer:
146+
_, err = io.Copy(v, res.Body)
147+
default:
148+
data, err := ioutil.ReadAll(res.Body)
149+
if err == io.EOF {
150+
err = nil // ignore EOF errors caused by empty response body
151+
}
152+
153+
decErr := json.Unmarshal(data, v)
154+
if decErr != nil {
155+
err = decErr
156+
// In case of JSON decode error, re-construct response body
157+
res.Body = ioutil.NopCloser(bytes.NewBuffer(data))
158+
}
159+
}
160+
return err
161+
}

proxy/deploy.go

+39-38
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"context"
99
"encoding/json"
1010
"fmt"
11-
"io/ioutil"
1211
"net/http"
1312
"time"
1413

@@ -57,29 +56,48 @@ func generateFuncStr(spec *DeployFunctionSpec) string {
5756
return spec.FunctionName
5857
}
5958

59+
type DeployResponse struct {
60+
Message string
61+
RollingUpdate bool
62+
URL string
63+
}
64+
6065
// DeployFunction first tries to deploy a function and if it exists will then attempt
6166
// a rolling update. Warnings are suppressed for the second API call (if required.)
62-
func (c *Client) DeployFunction(context context.Context, spec *DeployFunctionSpec) int {
67+
func (c *Client) DeployFunction(context context.Context, spec *DeployFunctionSpec) (*DeployResponse, *http.Response, error) {
6368

6469
rollingUpdateInfo := fmt.Sprintf("Function %s already exists, attempting rolling-update.", spec.FunctionName)
65-
statusCode, deployOutput := c.deploy(context, spec, spec.Update)
70+
res, err := c.deploy(context, spec, spec.Update)
71+
72+
if err != nil && IsUnknown(err) {
73+
return nil, res, err
74+
}
75+
76+
var deployResponse DeployResponse
77+
if err == nil {
78+
deployResponse.Message = fmt.Sprintf("Deployed. %s.", res.Status)
79+
deployResponse.URL = fmt.Sprintf("%s/function/%s", c.GatewayURL.String(), generateFuncStr(spec))
80+
}
6681

67-
if spec.Update == true && statusCode == http.StatusNotFound {
82+
if spec.Update == true && IsNotFound(err) {
6883
// Re-run the function with update=false
84+
res, err = c.deploy(context, spec, false)
85+
if err == nil {
86+
deployResponse.Message = fmt.Sprintf("Deployed. %s.", res.Status)
87+
deployResponse.URL = fmt.Sprintf("%s/function/%s", c.GatewayURL.String(), generateFuncStr(spec))
88+
}
89+
90+
} else if res.StatusCode == http.StatusOK {
91+
deployResponse.Message += rollingUpdateInfo
92+
deployResponse.RollingUpdate = true
6993

70-
statusCode, deployOutput = c.deploy(context, spec, false)
71-
} else if statusCode == http.StatusOK {
72-
fmt.Println(rollingUpdateInfo)
7394
}
74-
fmt.Println()
75-
fmt.Println(deployOutput)
76-
return statusCode
95+
96+
return &deployResponse, res, err
7797
}
7898

7999
// deploy a function to an OpenFaaS gateway over REST
80-
func (c *Client) deploy(context context.Context, spec *DeployFunctionSpec, update bool) (int, string) {
81-
82-
var deployOutput string
100+
func (c *Client) deploy(context context.Context, spec *DeployFunctionSpec, update bool) (*http.Response, error) {
83101
// Need to alter Gateway to allow nil/empty string as fprocess, to avoid this repetition.
84102
var fprocessTemplate string
85103
if len(spec.FProcess) > 0 {
@@ -146,37 +164,20 @@ func (c *Client) deploy(context context.Context, spec *DeployFunctionSpec, updat
146164
request, err = c.newRequest(method, "/system/functions", reader)
147165

148166
if err != nil {
149-
deployOutput += fmt.Sprintln(err)
150-
return http.StatusInternalServerError, deployOutput
167+
return nil, err
151168
}
152169

153170
res, err := c.doRequest(context, request)
154171

155172
if err != nil {
156-
deployOutput += fmt.Sprintln("Is OpenFaaS deployed? Do you need to specify the --gateway flag?")
157-
deployOutput += fmt.Sprintln(err)
158-
return http.StatusInternalServerError, deployOutput
159-
}
160-
161-
if res.Body != nil {
162-
defer res.Body.Close()
173+
errMessage := fmt.Sprintln("Is OpenFaaS deployed? Do you need to specify the --gateway flag?")
174+
errMessage += fmt.Sprintln(err)
175+
return res, NewUnknown(errMessage, 0)
163176
}
164177

165-
switch res.StatusCode {
166-
case http.StatusOK, http.StatusCreated, http.StatusAccepted:
167-
deployOutput += fmt.Sprintf("Deployed. %s.\n", res.Status)
168-
169-
deployedURL := fmt.Sprintf("URL: %s/function/%s", c.GatewayURL.String(), generateFuncStr(spec))
170-
deployOutput += fmt.Sprintln(deployedURL)
171-
case http.StatusUnauthorized:
172-
deployOutput += fmt.Sprintln("unauthorized access, run \"faas-cli login\" to setup authentication for this server")
173-
174-
default:
175-
bytesOut, err := ioutil.ReadAll(res.Body)
176-
if err == nil {
177-
deployOutput += fmt.Sprintf("Unexpected status: %d, message: %s\n", res.StatusCode, string(bytesOut))
178-
}
178+
err = checkForAPIError(res)
179+
if err != nil {
180+
return res, err
179181
}
180-
181-
return res.StatusCode, deployOutput
182+
return res, nil
182183
}

proxy/deploy_test.go

+34-28
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ type deployProxyTest struct {
2121
mockServerResponses []int
2222
replace bool
2323
update bool
24-
expectedOutput string
24+
expectedMessage string
25+
statusCode int
2526
}
2627

2728
func runDeployProxyTest(t *testing.T, deployTest deployProxyTest) {
@@ -34,32 +35,34 @@ func runDeployProxyTest(t *testing.T, deployTest deployProxyTest) {
3435
cliAuth := NewTestAuth(nil)
3536
proxyClient, _ := NewClient(cliAuth, s.URL, nil, &defaultCommandTimeout)
3637

37-
stdout := test.CaptureStdout(func() {
38-
proxyClient.DeployFunction(context.TODO(), &DeployFunctionSpec{
39-
"fprocess",
40-
"function",
41-
"image",
42-
"dXNlcjpwYXNzd29yZA==",
43-
"language",
44-
deployTest.replace,
45-
nil,
46-
"network",
47-
[]string{},
48-
deployTest.update,
49-
[]string{},
50-
map[string]string{},
51-
map[string]string{},
52-
FunctionResourceRequest{},
53-
false,
54-
tlsNoVerify,
55-
"",
56-
"",
57-
})
38+
dRes, httpRes, _ := proxyClient.DeployFunction(context.TODO(), &DeployFunctionSpec{
39+
"fprocess",
40+
"function",
41+
"image",
42+
"dXNlcjpwYXNzd29yZA==",
43+
"language",
44+
deployTest.replace,
45+
nil,
46+
"network",
47+
[]string{},
48+
deployTest.update,
49+
[]string{},
50+
map[string]string{},
51+
map[string]string{},
52+
FunctionResourceRequest{},
53+
false,
54+
tlsNoVerify,
55+
"",
56+
"",
5857
})
5958

60-
r := regexp.MustCompile(deployTest.expectedOutput)
61-
if !r.MatchString(stdout) {
62-
t.Fatalf("Output not matched: %s", stdout)
59+
if httpRes.StatusCode != deployTest.statusCode {
60+
t.Fatalf("StatuCode did not match. expected: %d, got: %d", deployTest.statusCode, httpRes.StatusCode)
61+
}
62+
63+
r := regexp.MustCompile(deployTest.expectedMessage)
64+
if !r.MatchString(dRes.Message) {
65+
t.Fatalf("Output not matched: %s", dRes.Message)
6366
}
6467
}
6568

@@ -70,21 +73,24 @@ func Test_RunDeployProxyTests(t *testing.T) {
7073
mockServerResponses: []int{http.StatusOK, http.StatusOK},
7174
replace: true,
7275
update: false,
73-
expectedOutput: `(?m:Deployed)`,
76+
statusCode: http.StatusOK,
77+
expectedMessage: `(?m:Deployed)`,
7478
},
7579
{
7680
title: "404_Deploy",
7781
mockServerResponses: []int{http.StatusOK, http.StatusNotFound},
7882
replace: true,
7983
update: false,
80-
expectedOutput: `(?m:Unexpected status: 404)`,
84+
statusCode: http.StatusNotFound,
85+
expectedMessage: "",
8186
},
8287
{
8388
title: "UpdateFailedDeployed",
8489
mockServerResponses: []int{http.StatusNotFound, http.StatusOK},
8590
replace: false,
8691
update: true,
87-
expectedOutput: `(?m:Deployed)`,
92+
statusCode: http.StatusOK,
93+
expectedMessage: `(?m:Deployed)`,
8894
},
8995
}
9096
for _, tst := range deployProxyTests {

0 commit comments

Comments
 (0)