Skip to content

enable httperror to return status code from server #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 68 additions & 27 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ func NewClient(gatewayURL *url.URL, auth ClientAuth, client *http.Client) *Clien
}
}

type HttpError struct {
Err error
Status int
}

func (e *HttpError) Error() string {
return e.Err.Error()
}

func createHttpError(err error, statusCode int) *HttpError {
return &HttpError{Err: err, Status: statusCode}
}

// GetNamespaces get openfaas namespaces
func (s *Client) GetNamespaces(ctx context.Context) ([]string, error) {
u := s.GatewayURL
Expand All @@ -54,7 +67,7 @@ func (s *Client) GetNamespaces(ctx context.Context) ([]string, error) {

res, err := s.Client.Do(req)
if err != nil {
return namespaces, fmt.Errorf("unable to make request: %w", err)
return namespaces, fmt.Errorf("unable to make HTTP request to OpenFaaS on URL: %s, error: %s", s.GatewayURL, err)
}

if res.Body != nil {
Expand All @@ -66,8 +79,15 @@ func (s *Client) GetNamespaces(ctx context.Context) ([]string, error) {
return namespaces, err
}

if res.StatusCode == http.StatusUnauthorized {
return namespaces, fmt.Errorf("check authorization, status code: %d", res.StatusCode)
switch res.StatusCode {
case http.StatusOK:
break

case http.StatusUnauthorized:
return namespaces, createHttpError(fmt.Errorf("unauthorized action, please setup authentication for this server"), res.StatusCode)

default:
return namespaces, createHttpError(fmt.Errorf("server returned unexpected status code %d, message: %q", res.StatusCode, string(bytesOut)), res.StatusCode)
}

if len(bytesOut) == 0 {
Expand Down Expand Up @@ -106,7 +126,7 @@ func (s *Client) GetFunctions(ctx context.Context, namespace string) ([]types.Fu

res, err := s.Client.Do(req)
if err != nil {
return []types.FunctionStatus{}, fmt.Errorf("unable to make HTTP request: %w", err)
return []types.FunctionStatus{}, fmt.Errorf("unable to make HTTP request to OpenFaaS on URL: %s, error: %s", s.GatewayURL, err)
}

if res.Body != nil {
Expand All @@ -115,6 +135,17 @@ func (s *Client) GetFunctions(ctx context.Context, namespace string) ([]types.Fu

body, _ := io.ReadAll(res.Body)

switch res.StatusCode {
case http.StatusAccepted, http.StatusOK:
break

case http.StatusUnauthorized:
return nil, createHttpError(fmt.Errorf("unauthorized action, please setup authentication for this server"), res.StatusCode)

default:
return nil, createHttpError(fmt.Errorf("server returned unexpected status code %d, message: %q", res.StatusCode, string(body)), res.StatusCode)
}

functions := []types.FunctionStatus{}
if err := json.Unmarshal(body, &functions); err != nil {
return []types.FunctionStatus{},
Expand Down Expand Up @@ -142,7 +173,7 @@ func (s *Client) GetInfo(ctx context.Context) (SystemInfo, error) {

res, err := s.Client.Do(req)
if err != nil {
return SystemInfo{}, fmt.Errorf("unable to make HTTP request: %w", err)
return SystemInfo{}, fmt.Errorf("unable to make HTTP request to OpenFaaS on URL: %s, error: %s", s.GatewayURL, err)
}

if res.Body != nil {
Expand Down Expand Up @@ -185,7 +216,7 @@ func (s *Client) GetFunction(ctx context.Context, name, namespace string) (types

res, err := s.Client.Do(req)
if err != nil {
return types.FunctionDeployment{}, fmt.Errorf("unable to make HTTP request: %w", err)
return types.FunctionDeployment{}, fmt.Errorf("unable to make HTTP request to OpenFaaS on URL: %s, error: %s", s.GatewayURL, err)
}

if res.Body != nil {
Expand All @@ -194,6 +225,17 @@ func (s *Client) GetFunction(ctx context.Context, name, namespace string) (types

body, _ := io.ReadAll(res.Body)

switch res.StatusCode {
case http.StatusOK:
break

case http.StatusUnauthorized:
return types.FunctionDeployment{}, createHttpError(fmt.Errorf("unauthorized action, please setup authentication for this server"), res.StatusCode)

default:
return types.FunctionDeployment{}, createHttpError(fmt.Errorf("server returned unexpected status code %d, message: %q", res.StatusCode, string(body)), res.StatusCode)
}

functions := types.FunctionDeployment{}
if err := json.Unmarshal(body, &functions); err != nil {
return types.FunctionDeployment{},
Expand All @@ -203,20 +245,19 @@ func (s *Client) GetFunction(ctx context.Context, name, namespace string) (types
return functions, nil
}

func (s *Client) Deploy(ctx context.Context, spec types.FunctionDeployment) (int, error) {
func (s *Client) Deploy(ctx context.Context, spec types.FunctionDeployment) error {
return s.deploy(ctx, http.MethodPost, spec)

}

func (s *Client) Update(ctx context.Context, spec types.FunctionDeployment) (int, error) {
func (s *Client) Update(ctx context.Context, spec types.FunctionDeployment) error {
return s.deploy(ctx, http.MethodPut, spec)
}

func (s *Client) deploy(ctx context.Context, method string, spec types.FunctionDeployment) (int, error) {
func (s *Client) deploy(ctx context.Context, method string, spec types.FunctionDeployment) error {

bodyBytes, err := json.Marshal(spec)
if err != nil {
return http.StatusBadRequest, err
return createHttpError(err, http.StatusBadRequest)
}

bodyReader := bytes.NewReader(bodyBytes)
Expand All @@ -226,18 +267,18 @@ func (s *Client) deploy(ctx context.Context, method string, spec types.FunctionD

req, err := http.NewRequestWithContext(ctx, method, u.String(), bodyReader)
if err != nil {
return http.StatusBadGateway, err
return fmt.Errorf("unable to create request for %s, error: %w", u.String(), err)
}

if s.ClientAuth != nil {
if err := s.ClientAuth.Set(req); err != nil {
return http.StatusInternalServerError, fmt.Errorf("unable to set Authorization header: %w", err)
return fmt.Errorf("unable to set Authorization header: %w", err)
}
}

res, err := s.Client.Do(req)
if err != nil {
return http.StatusBadGateway, err
return fmt.Errorf("unable to make HTTP request to OpenFaaS on URL: %s, error: %s", s.GatewayURL, err)
}

var body []byte
Expand All @@ -248,13 +289,13 @@ func (s *Client) deploy(ctx context.Context, method string, spec types.FunctionD

switch res.StatusCode {
case http.StatusAccepted, http.StatusOK, http.StatusCreated:
return res.StatusCode, nil
return nil

case http.StatusUnauthorized:
return res.StatusCode, fmt.Errorf("unauthorized action, please setup authentication for this server")
return createHttpError(fmt.Errorf("unauthorized action, please setup authentication for this server"), res.StatusCode)

default:
return res.StatusCode, fmt.Errorf("unexpected status code: %d, message: %q", res.StatusCode, string(body))
return createHttpError(fmt.Errorf("unexpected status code: %d, message: %q", res.StatusCode, string(body)), res.StatusCode)
}
}

Expand All @@ -280,7 +321,7 @@ func (s *Client) ScaleFunction(ctx context.Context, functionName, namespace stri

req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), bodyReader)
if err != nil {
return fmt.Errorf("cannot connect to OpenFaaS on URL: %s, error: %s", u.String(), err)
return fmt.Errorf("unable to create request for %s, error: %w", u.String(), err)
}

if s.ClientAuth != nil {
Expand All @@ -290,7 +331,7 @@ func (s *Client) ScaleFunction(ctx context.Context, functionName, namespace stri
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("cannot connect to OpenFaaS on URL: %s, error: %s", s.GatewayURL, err)
return fmt.Errorf("unable to make HTTP request to OpenFaaS on URL: %s, error: %s", s.GatewayURL, err)

}

Expand All @@ -303,10 +344,10 @@ func (s *Client) ScaleFunction(ctx context.Context, functionName, namespace stri
break

case http.StatusNotFound:
return fmt.Errorf("function %s not found", functionName)
return createHttpError(fmt.Errorf("function %s not found", functionName), res.StatusCode)

case http.StatusUnauthorized:
return fmt.Errorf("unauthorized action, please setup authentication for this server")
return createHttpError(fmt.Errorf("unauthorized action, please setup authentication for this server"), res.StatusCode)

default:
var err error
Expand All @@ -315,7 +356,7 @@ func (s *Client) ScaleFunction(ctx context.Context, functionName, namespace stri
return err
}

return fmt.Errorf("server returned unexpected status code %d, message: %q", res.StatusCode, string(bytesOut))
return createHttpError(fmt.Errorf("server returned unexpected status code %d, message: %q", res.StatusCode, string(bytesOut)), res.StatusCode)
}
return nil
}
Expand All @@ -338,7 +379,7 @@ func (s *Client) DeleteFunction(ctx context.Context, functionName, namespace str

req, err := http.NewRequestWithContext(ctx, http.MethodDelete, u.String(), bodyReader)
if err != nil {
return fmt.Errorf("cannot connect to OpenFaaS on URL: %s, error: %s", u.String(), err)
return fmt.Errorf("unable to create request for %s, error: %w", u.String(), err)
}

if s.ClientAuth != nil {
Expand All @@ -348,7 +389,7 @@ func (s *Client) DeleteFunction(ctx context.Context, functionName, namespace str
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("cannot connect to OpenFaaS on URL: %s, error: %s", s.GatewayURL, err)
return fmt.Errorf("unable to make HTTP request to OpenFaaS on URL: %s, error: %s", s.GatewayURL, err)

}

Expand All @@ -361,10 +402,10 @@ func (s *Client) DeleteFunction(ctx context.Context, functionName, namespace str
break

case http.StatusNotFound:
return fmt.Errorf("function %s not found", functionName)
return createHttpError(fmt.Errorf("function %s not found", functionName), res.StatusCode)

case http.StatusUnauthorized:
return fmt.Errorf("unauthorized action, please setup authentication for this server")
return createHttpError(fmt.Errorf("unauthorized action, please setup authentication for this server"), res.StatusCode)

default:
var err error
Expand All @@ -373,7 +414,7 @@ func (s *Client) DeleteFunction(ctx context.Context, functionName, namespace str
return err
}

return fmt.Errorf("server returned unexpected status code %d, message: %q", res.StatusCode, string(bytesOut))
return createHttpError(fmt.Errorf("server returned unexpected status code %d, message: %q", res.StatusCode, string(bytesOut)), res.StatusCode)
}
return nil
}
81 changes: 75 additions & 6 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func TestSdk_DeployFunction(t *testing.T) {
handler: func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusUnauthorized)
},
err: fmt.Errorf("unauthorized action, please setup authentication for this server"),
err: createHttpError(fmt.Errorf("unauthorized action, please setup authentication for this server"), http.StatusUnauthorized),
},
{
name: "unknown error",
Expand All @@ -118,7 +118,7 @@ func TestSdk_DeployFunction(t *testing.T) {
handler: func(rw http.ResponseWriter, req *http.Request) {
http.Error(rw, "unknown error", http.StatusInternalServerError)
},
err: fmt.Errorf("unexpected status code: %d, message: %q", http.StatusInternalServerError, "unknown error\n"),
err: createHttpError(fmt.Errorf("unexpected status code: %d, message: %q", http.StatusInternalServerError, "unknown error\n"), http.StatusInternalServerError),
},
}

Expand All @@ -130,7 +130,8 @@ func TestSdk_DeployFunction(t *testing.T) {

client := NewClient(sU, nil, http.DefaultClient)

_, err := client.Deploy(context.Background(), types.FunctionDeployment{
// _, err := client.Deploy(context.Background(), types.FunctionDeployment{
err := client.Deploy(context.Background(), types.FunctionDeployment{
Service: funcName,
Image: fmt.Sprintf("docker.io/openfaas/%s:latest", funcName),
Namespace: nsName,
Expand Down Expand Up @@ -168,7 +169,7 @@ func TestSdk_DeleteFunction(t *testing.T) {
handler: func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusNotFound)
},
err: fmt.Errorf("function %s not found", funcName),
err: createHttpError(fmt.Errorf("function %s not found", funcName), http.StatusNotFound),
},
{
name: "client not authorized",
Expand All @@ -177,7 +178,7 @@ func TestSdk_DeleteFunction(t *testing.T) {
handler: func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusUnauthorized)
},
err: fmt.Errorf("unauthorized action, please setup authentication for this server"),
err: createHttpError(fmt.Errorf("unauthorized action, please setup authentication for this server"), http.StatusUnauthorized),
},
{
name: "unknown error",
Expand All @@ -186,7 +187,7 @@ func TestSdk_DeleteFunction(t *testing.T) {
handler: func(rw http.ResponseWriter, req *http.Request) {
http.Error(rw, "unknown error", http.StatusInternalServerError)
},
err: fmt.Errorf("server returned unexpected status code %d, message: %q", http.StatusInternalServerError, string("unknown error\n")),
err: createHttpError(fmt.Errorf("server returned unexpected status code %d, message: %q", http.StatusInternalServerError, string("unknown error\n")), http.StatusUnauthorized),
},
}

Expand All @@ -205,3 +206,71 @@ func TestSdk_DeleteFunction(t *testing.T) {
})
}
}

func TestSdk_ScaleFunction(t *testing.T) {
funcName := "funct1"
nsName := "ns1"
tests := []struct {
name string
functionName string
namespace string
replicas uint64
err error
handler func(rw http.ResponseWriter, req *http.Request)
}{
{
name: "scale request accepted",
functionName: funcName,
namespace: nsName,
replicas: 0,
handler: func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusAccepted)
},
},
{
name: "function not found",
functionName: funcName,
namespace: nsName,
replicas: 0,
handler: func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusNotFound)
},
err: createHttpError(fmt.Errorf("function %s not found", funcName), http.StatusNotFound),
},
{
name: "client not authorized",
functionName: funcName,
namespace: nsName,
replicas: 0,
handler: func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusUnauthorized)
},
err: createHttpError(fmt.Errorf("unauthorized action, please setup authentication for this server"), http.StatusUnauthorized),
},
{
name: "unknown error",
functionName: funcName,
namespace: nsName,
replicas: 0,
handler: func(rw http.ResponseWriter, req *http.Request) {
http.Error(rw, "unknown error", http.StatusInternalServerError)
},
err: createHttpError(fmt.Errorf("server returned unexpected status code %d, message: %q", http.StatusInternalServerError, string("unknown error\n")), http.StatusUnauthorized),
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := httptest.NewServer(http.HandlerFunc(test.handler))

sU, _ := url.Parse(s.URL)

client := NewClient(sU, nil, http.DefaultClient)
err := client.ScaleFunction(context.Background(), test.functionName, test.namespace, test.replicas)

if !errors.Is(err, test.err) && err.Error() != test.err.Error() {
t.Fatalf("wanted %s, but got: %s", test.err, err)
}
})
}
}