netbox-community / go-netbox

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

Support filtering by custom fields #153

Closed tgoff closed 6 months ago

tgoff commented 1 year ago

This was requested in https://github.com/netbox-community/go-netbox/issues/125 but was closed due to a limitation in the NetBox REST API. However, it appears this is now supported in NetBox per: https://demo.netbox.dev/static/docs/rest-api/filtering/#filtering-by-custom-field

ArnoSen commented 1 year ago

I got this working yesterday. It can be done with the current version of go-netbox. I will use my usecase as an example to get it done: I need to filter tennants on a customfield. The challenge is to get the url to change to "https://?cf_="

This can by defining a ClientOption.

Normallly you would so something like:

searchTennatsResponse, err := myClient.Tenancy.TenancyTenantsList(tenancy.NewTenancyTenantsListParams(), nil)

You can set predefined attributes on NewTenancyTenantsListParams with a number of ".With" methods. In order to get a custom field search in, you need to override some parts of TenancyTenantsListParams. You can do this using a CilentOption.

The source code of TenancyTenantsList reads:

func (a *Client) TenancyTenantsList(params *TenancyTenantsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*TenancyTenantsListOK, error) {
        if params == nil {
                params = NewTenancyTenantsListParams()
        }
        op := &runtime.ClientOperation{
                ID:                 "tenancy_tenants_list",
                Method:             "GET",
                PathPattern:        "/tenancy/tenants/",
                ProducesMediaTypes: []string{"application/json"},
                ConsumesMediaTypes: []string{"application/json"},
                Schemes:            []string{"http"},
                Params:             params,
                Reader:             &TenancyTenantsListReader{formats: a.formats},
                AuthInfo:           authInfo,
                Context:            params.Context,
                Client:             params.HTTPClient,
        }
        for _, opt := range opts {
                opt(op)
        }

By providing a ClientOption you can overrride the Params property of *runtime.ClientOperation. This property is of type ClientRequestWriter which is an interface with one method "WriteToRequest(ClientRequest, strfmt.Registry) error"

Checking the source code on NewTenancyTenantsListParams() that also implements ClientRequestWriter gives a good idea what needs to be done:

unc (o *TenancyTenantsListParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {

        if err := r.SetTimeout(o.timeout); err != nil {
                return err
        }
        var res []error

        if o.Contact != nil {

                // query param contact
                var qrContact string

                if o.Contact != nil {
                        qrContact = *o.Contact
                }
                qContact := qrContact
                if qContact != "" {

                        if err := r.SetQueryParam("contact", qContact); err != nil {
                                return err
                        }
                }
        }

WriteToRequest converts an object into an actual request. The code that needs to be called for a custom field filtering is r.SetQueryParam.

To make the search by custom field happen, a ClientOption needs to be defined. ClientOption is of type func(*runtime.ClientOperation).

So all combined in my case makes:

type CustomFieldFilter struct {
        CustomFieldName string
        CustomValue     string
}

func newCustomFieldFilter(customFieldName, customVal string) *CustomFieldFilter {
        return &searchByAccountNumber{
                CustomFieldName: customFieldName,
                CustomValue:     customVal,
        }
}       

func (o *CustomFieldFilter) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
        if err := r.SetQueryParam(fmt.Sprintf("cf_%s", url.QueryEscape(o.CustomFieldName)), o.CustomValue); err != nil {
                return err
        }
        return nil
}

func queryNetbox() {
        (...)
        setCustomTenanntSearch := func(co *runtime.ClientOperation) {
                //overwrite params with our search
                co.Params = newCustomFieldFilter(fieldname, fieldvalue)
        }

        searchTennatsResponse, err := llClient.Tenancy.TenancyTenantsList(tenancy.NewTenancyTenantsListParamsWithContext(ctx), nil, setCustomTennantSearch)
        (...)
ArnoSen commented 1 year ago

One important thing that cost me some time is that the name of the custom field is not what you see in the gui. I recommend that you get an object first and observe the exact name of the custom field.

tgoff commented 1 year ago

thanks for the thorough response! Ill give that a shot and see if I can get that to work in my use case

v0ctor commented 6 months ago

A new alpha version has been released with a different software to generate the library, so hopefully this bug has been resolved.

Please feel free to test it and to provide feedback.

softengineer commented 3 months ago

Is there any thought to implement custom fields search in alpha_version ? can't have a way to pass cf_ filter into its search method