netbox-community / go-netbox

The official Go API client for Netbox IPAM and DCIM service.
Other
195 stars 146 forks source link

Question about errors #64

Closed smutel closed 3 years ago

smutel commented 4 years ago

Hello,

I am using this library to create a terraform provider.
The error of this library is sent to the terraform provider. The only error message displayed by my terraform provider is like:

Error: unknown error (status 400): {resp:0xc0008ae870}

However when I am doing a tcpdump I see that we can have more human readable message like:

{"detail":"Unable to delete object. The following dependent objects were found: 192.168.56.0/24 (ipam.prefix)"}

Do we have this kind of message in the error object returned by this library ?

Thanks.

kobayashi commented 4 years ago

Could you show me your code to reproduce?

smutel commented 4 years ago

Here you can find that I return the error object to terraform and terraform will be in charge of displaying the message. I don't know what function on the error object is using by terraform.

https://github.com/smutel/terraform-provider-netbox/blob/init-the-project/netbox/resource_netbox_tenancy_tenant.go#L205

fbreckle commented 4 years ago

I have the same issue. Not having the actual error message is really annoying.

I dug around a bit. Here is my test code (with netbox-docker running locally)

package main

import (
    "github.com/go-openapi/runtime"
    httptransport "github.com/go-openapi/runtime/client"
    "github.com/netbox-community/go-netbox/netbox/client"
    "github.com/netbox-community/go-netbox/netbox/client/virtualization"
    log "github.com/sirupsen/logrus"
    "io/ioutil"
)

func main() {
    token := "0123456789abcdef0123456789abcdef01234567"
    netboxHost := "0.0.0.0:8000"
    transport := httptransport.New(netboxHost, client.DefaultBasePath, []string{"http"})
    transport.DefaultAuthentication = httptransport.APIKeyAuth("Authorization", "header", "Token "+token)
    c := client.New(transport, nil)
    req := virtualization.NewVirtualizationVirtualMachinesReadParams()
    req.ID = 2 // this machine is nonexistent, provoking an error in the next call
    res, err := c.Virtualization.VirtualizationVirtualMachinesRead(req, nil)
    if err != nil {
        log.Printf("first err: %v\n", err)
        log.Printf("Type of first err: %T\n", err)
        apiError := err.(*runtime.APIError)
        log.Printf("Type of apiError.Response: %T\n", apiError.Response)
        clientResponse := apiError.Response.(runtime.ClientResponse)
        log.Printf("clientResponse.Message: %v\n", clientResponse.Message())
        log.Printf("clientResponse.Body: %v\n", clientResponse.Body())
        log.Printf("Type of clientResponse.Body: %T\n", clientResponse.Body())
        body, err := ioutil.ReadAll(clientResponse.Body())
        if err != nil {
            log.Fatalf("Error while reading body: %v", err)
        }
        bodyString := string(body)
        log.Fatalf("Error while reading VM: %v", bodyString)
    }
    // wont get here
    log.Printf("%s\n", res)
    log.Printf("%s\n", res.Payload)
}

Output:

$ go run .
INFO[0000] first err: unknown error (status 404): {resp:0xc0001241b0}  
INFO[0000] Type of first err: *runtime.APIError         
INFO[0000] Type of apiError.Response: client.response   
INFO[0000] clientResponse.Message: 404 Not Found        
INFO[0000] clientResponse.Body: &{0xc000428040 {0 0} true <nil> 0x6dc900 0x6dc890} 
INFO[0000] Type of clientResponse.Body: *http.bodyEOFSignal 
FATA[0000] Error while reading body: http: read on closed response body 
exit status 1

Fun fact: When setting DEBUG=1 via env, it works:

$ DEBUG=1 go run .
GET /api/virtualization/virtual-machines/2/ HTTP/1.1
Host: 0.0.0.0:8000
User-Agent: Go-http-client/1.1
Accept: application/json
Authorization: Token 0123456789abcdef0123456789abcdef01234567
Accept-Encoding: gzip

HTTP/1.1 404 Not Found
Content-Length: 23
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Api-Version: 2.8
Connection: keep-alive
Content-Type: application/json
Date: Tue, 16 Jun 2020 20:22:13 GMT
Server: nginx
Vary: Accept, Cookie, Origin
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN

{"detail":"Not found."}
INFO[0000] first err: unknown error (status 404): {resp:0xc00059def0}  
INFO[0000] Type of first err: *runtime.APIError         
INFO[0000] Type of apiError.Response: client.response   
INFO[0000] clientResponse.Message: 404 Not Found        
INFO[0000] clientResponse.Body: {{"detail":"Not found."}} 
INFO[0000] Type of clientResponse.Body: ioutil.nopCloser 
FATA[0000] Error while reading VM: {"detail":"Not found."} 
exit status 1

I think this issue is related: https://github.com/go-swagger/go-swagger/issues/1470

joakimhellum commented 4 years ago

Hi,

Currently we cannot obtain the underlying http.Response if the StatusCode is not defined in the swagger spec. I guess it's possible to implement some workaround which allows http.Response.Body to be accessed anyway, but this feels very hacky.

For example:

func (e *ErrorTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    res, err := e.rt.RoundTrip(req)
    if res != nil {
        _, _ = httputil.DumpResponse(res, res.Body != nil)
    }

    return res, err
}

References: https://github.com/netbox-community/netbox/issues/4986