Git Product home page Git Product logo

goeapi's Introduction

Arista Go eAPI Library Build Status codecov.io GoDoc

Table of Contents

  1. Overview
  2. Installation
  3. Upgrading
  4. Getting Started
  5. Building Local Documentation
  6. Testing
  7. Contributing
  8. License

Overview

The Go Client for eAPI provides a native Go implementation for programming Arista EOS network devices using Golang. The Go client provides the ability to build native applications in Go that can communicate with EOS remotely over a HTTP/S transport (off-box). It uses a standard INI-style configuration file to specifiy one or more connection profiles.

The goeapi implemenation also provides an API layer for building native Go objects that allow for configuration and state extraction of EOS nodes. The API layer provides a consistent implementation for working with EOS configuration resources. The implementation of the API layer is highly extensible and can be used as a foundation for building custom data models.

The libray is freely provided to the open source community for building robust applications using Arista EOS eAPI. Support is provided as best effort through Github iusses.

Requirements

  • Arista EOS v4.12 or later
  • Arista eAPI enabled for either http or https
  • Go 1.5+

Installation

First, it is assumed you have and are working in a standard Go workspace, as described in http://golang.org/doc/code.html, with proper GOPATH set. Go 1.5+ is what's recommended for using goeapi. To download and install goeapi:

$ go get github.com/aristanetworks/goeapi

After setting up Go and installing goeapi, any required build tools can be installed by bootstrapping your environment via:

$ make bootstrap

Upgrading

$ go get -u github.com/aristanetworks/goeapi

Getting Started

The following steps need to be followed to assure successful configuration of goeapi.

  1. EOS Command API must be enabled

    To enable EOS Command API from configuration mode, configure proper protocol under management api, and then verify:

        Switch# configure terminal
        Switch(config)# management api http-commands
        Switch(config-mgmt-api-http-cmds)# protocol ?
          http         Configure HTTP server options
          https        Configure HTTPS server options
          unix-socket  Configure Unix Domain Socket
        Switch(config-mgmt-api-http-cmds)# protocol http
        Switch(config-mgmt-api-http-cmds)# end
    
        Switch# show management api http-commands
        Enabled:            Yes
        HTTPS server:       running, set to use port 443
        HTTP server:        running, set to use port 80
        Local HTTP server:  shutdown, no authentication, set to use port 8080
        Unix Socket server: shutdown, no authentication
        ...
    

2. Create configuration file with proper node properties. (*See eapi.conf file examples below*)

    **Note:** The default search path for the conf file is ``~/.eapi.conf``
    followed by ``/mnt/flash/eapi.conf``.   This can be overridden by setting
    ``EAPI_CONF=<path file conf file>`` in your environment.

## Example eapi.conf File
Below is an example of an eAPI conf file.  The conf file can contain more than
one node.  Each node section must be prefaced by **connection:\<name\>** where
\<name\> is the name of the connection.

The following configuration options are available for defining node entries:

* **host** - The IP address or FQDN of the remote device.  If the host parameter is omitted then the connection name is used
* **username** - The eAPI username to use for authentication (only required for http or https connections)
* **password** - The eAPI password to use for authentication (only required for http or https connections)
* **enablepwd** - The enable mode password if required by the destination node
* **transport** - Configures the type of transport connection to use.  The default value is _https_.  Valid values are:
  * http
  * https
  * socket
* **port** - Configures the port to use for the eAPI connection. (Currently Not Implemented)

_Note:_ See the EOS User Manual found at arista.com for more details on configuring eAPI values.

# Using Goeapi
Once goeapi has been installed and your .eapi.config file is setup correctly, you are now ready to try it out. Here is a working example of .eapi.config file and go program:

```sh
$ cat ~/.eapi.config
[connection:arista1]
host=arista1
username=admin
password=root
enablepwd=passwd
transport=https
$ cat example1.go
package main

import (
        "fmt"

        "github.com/aristanetworks/goeapi"
        "github.com/aristanetworks/goeapi/module"
)

func main() {
        // connect to our device
        node, err := goeapi.ConnectTo("Arista1")
        if err != nil {
                panic(err)
        }
        // get the running config and print it
        conf := node.RunningConfig()
        fmt.Printf("Running Config:\n%s\n", conf)

        // get api system module
        sys := module.System(node)
        // change the host name to "Ladie"
        if ok := sys.SetHostname("Ladie"); !ok {
                fmt.Printf("SetHostname Failed\n")
        }
        // get system info
        sysInfo := sys.Get()
        fmt.Printf("\nSysinfo: %#v\n", sysInfo.HostName())
}

goeapi provides a way for users to directly couple a command with a predefined response. The underlying api will issue the command and the response stored in the defined type. For example, lets say the configured vlan ports are needed for some form of processing. If we know the JSON response for the command composed like the following: (from Arista Command API Explorer):

 {
    "jsonrpc": "2.0",
    "result": [
       {
          "sourceDetail": "",
          "vlans": {
             "2": {
                "status": "active",
                "name": "VLAN0002",
                "interfaces": {
                   "Port-Channel10": {
                      "privatePromoted": false
                   },
                   "Ethernet2": {
                      "privatePromoted": false
                   },
                   "Ethernet1": {
                      "privatePromoted": false
                   },
                   "Port-Channel5": {
                      "privatePromoted": false
                   }
                },
                "dynamic": false
             },
          }
       }
    ],
    "id": "CapiExplorer-123"
 }

We can then build our Go structures based on the response format and couple our show command with the type:

type MyShowVlan struct {
        SourceDetail string          `json:"sourceDetail"`
        Vlans        map[string]Vlan `json:"vlans"`
}

type Vlan struct {
        Status     string               `json:"status"`
        Name       string               `json:"name"`
        Interfaces map[string]Interface `json:"interfaces"`
        Dynamic    bool                 `json:"dynamic"`
}

type Interface struct {
        Annotation      string `json:"annotation"`
        PrivatePromoted bool   `json:"privatePromoted"`
}

func (s *MyShowVlan) GetCmd() string {
        return "show vlan configured-ports"
}

Since the command show vlan configured-ports is coupled with the response structure, the underlying api knows to issue the command and the response needs to be filled in. The resulting code looks like:

package main

import (
        "fmt"

        "github.com/aristanetworks/goeapi"
)

type MyShowVlan struct {
        SourceDetail string          `json:"sourceDetail"`
        Vlans        map[string]Vlan `json:"vlans"`
}

type Vlan struct {
        Status     string               `json:"status"`
        Name       string               `json:"name"`
        Interfaces map[string]Interface `json:"interfaces"`
        Dynamic    bool                 `json:"dynamic"`
}

type Interface struct {
        Annotation      string `json:"annotation"`
        PrivatePromoted bool   `json:"privatePromoted"`
}

func (s *MyShowVlan) GetCmd() string {
        return "show vlan configured-ports"
}

func main() {
        node, err := goeapi.ConnectTo("dut")
        if err != nil {
                panic(err)
        }

        sv := &MyShowVlan{}

        handle, _ := node.GetHandle("json")
        handle.AddCommand(sv)
        if err := handle.Call(); err != nil {
                panic(err)
        }

        for k, v := range sv.Vlans {
                fmt.Printf("Vlan:%s\n", k)
                fmt.Printf("  Name  : %s\n", v.Name)
                fmt.Printf("  Status: %s\n", v.Status)
        }
}

Also, if several commands/responses have been defined, goeapi supports command stacking to batch issue all at once:

    ...
	handle, _ := node.GetHandle("json")
	handle.AddCommand(showVersion)
	handle.AddCommand(showVlan)
	handle.AddCommand(showHostname)
	handle.AddCommand(showIp)
	if err := handle.Call(); err != nil {
		panic(err)
	}
	fmt.Printf("Version           : %s\n", showVersion.Version)
	fnt.Printf("Hostname          : %s\n", showHostname.Hostname)
    ...

There are several go example's using goeapi (as well as example .eapi.config file) provided in the examples directory.

Building Local Documentation

Documentation can be generated locally in plain text via:

$ godoc github.com/aristanetworks/goeapi

Or you can run the local godoc server and view the html version of the documentation by pointing your browser at http://localhost:6060

$ make doc
    or
$ godoc -http=:6060 -index

Testing

The goeapi library provides various tests. To run System specific tests, you will need to update the dut.conf file (found in testutils/fixtures) to include the device level specifics for your setup. The switch used for testing should have at least interfaces Ethernet1-7.

  • For running System tests, issue the following from the root of the goeapi directory:
$ make systest
    or
$ go test ./... -run SystemTest$
  • Similarly, Unit tests can be run via:
$ make unittest
    or
$ go test ./... -run UnitTest$

Verbose mode can be specified as a flag to provide additional information:

$ make GOTEST_FLAGS=-v test

Note: Test cases for XXX.go files live in respective XXX_test.go files and have the following function signature:

  • Unit Tests: TestXXX_UnitTest(t *testing.T){...
  • System Tests: TestXXX_SystemTest(t *testing.T){...

Any tests written must conform to this standard.

Contributing

Contributing pull requests are gladly welcomed for this repository. Please note that all contributions that modify the library behavior require corresponding test cases otherwise the pull request will be rejected.

License

Copyright (c) 2015-2016, Arista Networks Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  • Neither the name of Arista Networks nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

goeapi's People

Contributors

7ac avatar cheynearista avatar golint-fixer avatar sfunkhouser avatar yzguy avatar zlesnr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

goeapi's Issues

Does not work when using SOCKS5 proxy

Solution:

In eapilib.go, http.Transport is being initialized to set TLSClientConfig. Adding one line would resolve this issue.

 func (conn *HTTPSEapiConnection) send(data []byte) (*JSONRPCResponse, error) {
     ...
     tr := &http.Transport{
+        Proxy: http.ProxyFromEnvironment,   // adding this line resolves the issue
         TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
     }
 }

Request you to make this change.

Refer to DefaultTransport in https://golang.org/pkg/net/http/

VRF Type has ASN as int64 type but EAPI returns a string type

package main

import (
"fmt"

"github.com/aristanetworks/goeapi"
"github.com/aristanetworks/goeapi/module"

)

func main() {
node, err := goeapi.ConnectTo("sp1")
if err != nil {
panic(err)
}
s := module.Show(node)
showData, err := s.ShowIPBGPSummary()
if err != nil {
fmt.Println(err)
}
fmt.Printf("%v", showData.VRFs)
}

When running I received the following error:

1 error(s) decoding:

  • 'VRFs[default].asn' expected type 'int64', got unconvertible type 'string', value: '65001'

Looking through the code base, I see the following struct:

type VRF struct {
RouterID string json:"routerId"
Peers map[string]BGPNeighborSummary json:"peers"
VRF string json:"vrf"
ASN int64 json:"asn"
}

It may be safer to expect a string type

Handle.Call() does not return error when a command fails

As described in the comments for Handle.Call(), it only errors out when
error if handle is invalid, or problem encountered during sending or receiveing. .

However, when a command fails because e.g. a syntax error or an object that is not there, Handle.Call() will not error out. After Handle.Call() has been called, it is not possible anymore to get the error related to a failed command.

I see a couple of possible solutions:
1 Handle.Call() will return an error, next to communication errors, if any of the commands failed.
2 Handle.Call() will return an array of errors so the developer can find out which command failed more easily.

Changing the Handle.Call can break the API, so I would suggest to implement new methods:

  • CallWithCommandError() (error): this will return an error when a command failed.
  • CallWithCommandErrors(error, []error): this will return the current error object together with a slice of errors one for each command.

What do you think?

Incorrect error handling for the JSON response

When wrong credentials are given during arista node connection, HTTP response code indicates an error ( StatusCode:401, Status: "401 Unauthorized"), but, decodeEapiResponse does not set Error after decoding HTTP response. Instead, the function sets 'JSONRPCResponse.Error' to 'nil' (i.e success to the call)

Callers of 'HTTPSEapiConnection.send', HTTPSEapiConnection.Execute, EapiReqHandle.Call
misinterpret the response.

Have you noticed the issue? if so, can you please provide the fix?

Sample eapi.conf with wrong credentials

[LP-CARISE-OSX:~] $ cat ~/.eapi.conf
[connection:arista-dev]
host=192.168.27.230
username=eapi
password=Dankberg
enablepwd=Dankberg
transport=https
port=443

HTTP response

[LP-CARISE-OSX:~/projects/src/myproject] $ ./debug
======> &http.Response{Status:"401 Unauthorized", StatusCode:401, Proto:"HTTP/1.0", ProtoMajor:1, ProtoMinor:0, Header:http.Header{"Server":[]string{"BaseHTTP/0.3 Python/2.7"}, "Date":[]string{"Mon, 30 Jan 2017 21:54:12 GMT"}, "Cache-Control":[]string{"no-store", "no-cache", "must-revalidate", "max-age=0", "pre-check=0", "post-check=0"}, "Pragma":[]string{"no-cache"}, "Www-Authenticate":[]string{"Basic realm="COMMAND_API_AUTH""}, "Content-Type":[]string{"text/plain"}, "Content-Length":[]string{"62"}, "Connection":[]string{"close"}}, Body:(*http.cancelTimerBody)(0xc42012a100), ContentLength:62, TransferEncoding:[]string(nil), Close:true, Uncompressed:false, Trailer:http.Header(nil), Request:(*http.Request)(0xc4200a63c0), TLS:(*tls.ConnectionState)(0xc4200c04d0)}
2017/01/30 14:09:28 invalid character 'U' looking for beginning of value
chakri --> &goeapi.JSONRPCResponse{Jsonrpc:"", Result:[]map[string]interface {}(nil), ID:0, Error:(*goeapi.RespError)(nil)}
Version:
======> &http.Response{Status:"401 Unauthorized", StatusCode:401, Proto:"HTTP/1.0", ProtoMajor:1, ProtoMinor:0, Header:http.Header{"Cache-Control":[]string{"no-store", "no-cache", "must-revalidate", "max-age=0", "pre-check=0", "post-check=0"}, "Pragma":[]string{"no-cache"}, "Www-Authenticate":[]string{"Basic realm="COMMAND_API_AUTH""}, "Content-Type":[]string{"text/plain"}, "Content-Length":[]string{"62"}, "Connection":[]string{"close"}, "Server":[]string{"BaseHTTP/0.3 Python/2.7"}, "Date":[]string{"Mon, 30 Jan 2017 21:54:13 GMT"}}, Body:(*http.cancelTimerBody)(0xc42011b340), ContentLength:62, TransferEncoding:[]string(nil), Close:true, Uncompressed:false, Trailer:http.Header(nil), Request:(*http.Request)(0xc4200a6a50), TLS:(*tls.ConnectionState)(0xc4200c06e0)}
2017/01/30 14:09:29 invalid character 'U' looking for beginning of value
chakri --> &goeapi.JSONRPCResponse{Jsonrpc:"", Result:[]map[string]interface {}(nil), ID:0, Error:(*goeapi.RespError)(nil)}
Modelname :
Internal Version :
System MAC :
Serial Number :
Mem Total : 0
Bootup Timestamp : 0.00
Mem Free : 0
Version :
Architecture :
Internal Build ID :
Hardware Revision :

On the Same note, for the correct credentials, the below is the HTTP response

[LP-CARISE-OSX:~/projects/src/myproject] $ ./debug
======> &http.Response{Status:"200 OK", StatusCode:200, Proto:"HTTP/1.0", ProtoMajor:1, ProtoMinor:0, Header:http.Header{"Date":[]string{"Mon, 30 Jan 2017 21:53:45 GMT"}, "Cache-Control":[]string{"no-store", "no-cache", "must-revalidate", "max-age=0", "pre-check=0", "post-check=0"}, "Pragma":[]string{"no-cache"}, "Content-Type":[]string{"application/json"}, "Content-Length":[]string{"384"}, "Connection":[]string{"close"}, "Server":[]string{"BaseHTTP/0.3 Python/2.7"}}, Body:(*http.cancelTimerBody)(0xc4201280c0), ContentLength:384, TransferEncoding:[]string(nil), Close:true, Uncompressed:false, Trailer:http.Header(nil), Request:(*http.Request)(0xc42009a3c0), TLS:(*tls.ConnectionState)(0xc42001a630)}
chakri --> &goeapi.JSONRPCResponse{Jsonrpc:"2.0", Result:[]map[string]interface {}{map[string]interface {}{}, map[string]interface {}{"modelName":"vEOS", "internalVersion":"4.13.14M-2777234.41314M.1", "systemMacAddress":"12:20:96:30:92:82", "serialNumber":"", "version":"4.13.14M", "hardwareRevision":"", "memTotal":4.024932e+06, "bootupTimestamp":1.48519117951e+09, "memFree":2.581584e+06, "architecture":"i386", "internalBuildId":"b4c6e98e-0021-4681-abeb-fbfa1f39d02a"}}, ID:14670, Error:(*goeapi.RespError)(nil)}
Version: 4.13.14M
======> &http.Response{Status:"200 OK", StatusCode:200, Proto:"HTTP/1.0", ProtoMajor:1, ProtoMinor:0, Header:http.Header{"Cache-Control":[]string{"no-store", "no-cache", "must-revalidate", "max-age=0", "pre-check=0", "post-check=0"}, "Pragma":[]string{"no-cache"}, "Content-Type":[]string{"application/json"}, "Content-Length":[]string{"384"}, "Connection":[]string{"close"}, "Server":[]string{"BaseHTTP/0.3 Python/2.7"}, "Date":[]string{"Mon, 30 Jan 2017 21:53:46 GMT"}}, Body:(*http.cancelTimerBody)(0xc4201640c0), ContentLength:384, TransferEncoding:[]string(nil), Close:true, Uncompressed:false, Trailer:http.Header(nil), Request:(*http.Request)(0xc42009aa50), TLS:(*tls.ConnectionState)(0xc42001a840)}
chakri --> &goeapi.JSONRPCResponse{Jsonrpc:"2.0", Result:[]map[string]interface {}{map[string]interface {}{}, map[string]interface {}{"systemMacAddress":"12:20:96:30:92:82", "serialNumber":"", "memTotal":4.024932e+06, "memFree":2.581584e+06, "architecture":"i386", "internalBuildId":"b4c6e98e-0021-4681-abeb-fbfa1f39d02a", "hardwareRevision":"", "modelName":"vEOS", "bootupTimestamp":1.48519117951e+09, "version":"4.13.14M", "internalVersion":"4.13.14M-2777234.41314M.1"}}, ID:14670, *Error:(goeapi.RespError)(nil)}
Modelname : vEOS
Internal Version : 4.13.14M-2777234.41314M.1
System MAC : 12:20:96:30:92:82
Serial Number :
Mem Total : 4024932
Bootup Timestamp : 1485191179.51
Mem Free : 2581584
Version : 4.13.14M
Architecture : i386
Internal Build ID : b4c6e98e-0021-4681-abeb-fbfa1f39d02a
Hardware Revision :

=========================================
Call flow for reference

func (handle *EapiReqHandle) Call() error {
....
jsonrsp, err := handle.node.conn.Execute(commands, handle.encoding)
if err != nil {
return err
}

....

}

func (conn *HTTPSEapiConnection) Execute(commands []interface{},
encoding string) (*JSONRPCResponse, error) {
if conn == nil {
return &JSONRPCResponse{}, fmt.Errorf("No connection")
}
conn.ClearError()
data, err := buildJSONRequest(commands, encoding, os.Getpid())
if err != nil {
conn.SetError(err)
return &JSONRPCResponse{}, err
}
return conn.send(data)
}

// decodeEapiResponse [private] Used to decode JSON Response into
// structure format defined by type JSONRPCResponse
func decodeEapiResponse(resp *http.Response) *JSONRPCResponse {
dec := json.NewDecoder(resp.Body)
var v JSONRPCResponse
if err := dec.Decode(&v); err != nil {
log.Println(err)
}
return &v
}

func (conn *HTTPSEapiConnection) send(data []byte) (*JSONRPCResponse, error) {
{
...
...

resp, err := client.Post(url, "application/json", bytes.NewReader(data))
...

**# jsonRsp := decodeEapiResponse(resp)**

// check for errors in the JSON response
if jsonRsp.Error != nil {
	err := fmt.Errorf("JSON Error(%d): %s", jsonRsp.Error.Code,
		jsonRsp.Error.Message)
	conn.SetError(err)
	return jsonRsp, err
}

fmt.Printf("chakri --> %#v", jsonRsp)
return jsonRsp, nil

}

The issue is very easily reproducible with wrong credentials with show-version.go (arista sample code).
Thanks
Chakri

Errors Authenticating with Passwords Containing Special Characters

I was playing around with the package today, noticed that I was getting an error connecting to my username, eg. johndoe: no such host, after some digging it looks like it's an issue with handling around special characters in passwords.

Code to reproduce

// not actually my password :D
passwords := []string{"#$hu7u9d0od", "%$hu7u9d0od", "^$hu7u9d0od"}

for _, password := range passwords {
  fmt.Printf("Connecting to %s\n", device)
  node, err := goeapi.Connect("https", "arista-switch.domain.com", "user", password, 443)
  if err != nil {
    fmt.Println(err)
  }

  fmt.Println("Enabling...")
  node.EnableAuthentication("enablepassword")

  fmt.Println("Getting running configuration...")
  conf := node.RunningConfig()
  fmt.Println(conf)

  // Simply test that the node is available
  handle, err := node.GetHandle("json")
  defer handle.Close()
  err = handle.Call()
  if err != nil {
	 fmt.Println(err)
  }

  log.Printf("Connected to? %s\n", device)
}

Results:

Connecting to arista-switch.domain.com
Enabling...
Getting running configuration...

Post https://johndoe#[email protected]:443/command-api: dial tcp: lookup johndoe: no such host
2018/05/04 15:11:02 Connected to? arista-switch.domain.com
Connecting to arista-switch.domain.com
Enabling...
Getting running configuration...

parse https://johndoe:%[email protected]:443/command-api: invalid URL escape "%$h"
2018/05/04 15:11:02 Connected to? arista-switch.domain.com
Connecting to arista-switch.domain.com
Enabling...
Getting running configuration...

parse https://johndoe:^[email protected]:443/command-api: net/url: invalid userinfo
2018/05/04 15:11:02 Connected to? arista-switch.domain.com

I tested !@#$%^&*()_+ and the ones above were the only ones which caused an issue.

Possible connection leak?

I'm seeing issues where connections are not cleaned up after a request is issued. In a case I'm working on, a connection to several hundred devices exhausts my local open file handle limit and causes the program to stop functioning.

while true; do lsof -i -n -P | grep main | awk '{ print $9 }' |awk -F'>' '{print $2}' | sort | uniq -c | sort -n ; sleep 1; echo;done

This yields something like the following:

   5 192.168.10.17:443
   5 192.168.10.24:443
   5 192.168.10.30:443
   5 192.168.10.36:443
   5 192.168.10.38:443
   5 192.168.10.40:443
   5 192.168.10.48:443
   5 192.168.10.50:443
   5 192.168.10.54:443

This indicates that there are 5 connections to each host I'm connected to, which I believe is the result of the HTTPs connection pooling provided by http.Client. Though the decode of the JSON doesn't appear to perform the ReadAll necessary to clean up the client connections to the given host. Note also that for a given run, each send() call creates a new client, which I believe further exacerbates the issue in my conditions here. It's late on a Friday and I'm heading into the weekend, but wanted to make a note.

Cannot connect

Hello.

I have been trying to connect using both Connect and ConnectTo without success.

  • I do have the .eapi.conf setup properly
  • Using the Python client works fine without any change.

When using the ConnectFor, I see that Connections is set to [], and I get the following error : panic: Connection profile not found in config (the same .eapi.conf at the same location works fine with pyeapi)

When using the Connect, I don't get any error, but the node variable is empty, it looks like it's not trying to connect at all ?

Any idea of what could be the issue ?

Thank you.

Feature Request: Ability to retrieve configured interface speed

Currently there doesn't seem to be a way to get the configured interface speed, only the active interface speed. Is there a simple way to achieve this? The current SwitchPort code looks to grab the entire config and do some regex on the results, which is a bit slow, and also missing the speed data.

API response too big

I am calling eapi with command "show ip route bgp", the response is too big for my container so that docker container crash every time. Any good idea to handle this case?
Thanks!

Bug: JSON tags ignored

I ran your example program in README.md in the root:

package main

import (
        "fmt"

        "github.com/aristanetworks/goeapi"
)

type MyShowVlan struct {
        SourceDetail string          `json:"sourceDetail"`
        Vlans        map[string]Vlan `json:"vlans"`
}

type Vlan struct {
        Status     string               `json:"status"`
        Name       string               `json:"name"`
        Interfaces map[string]Interface `json:"interfaces"`
        Dynamic    bool                 `json:"dynamic"`
}

type Interface struct {
        Annotation      string `json:"annotation"`
        PrivatePromoted bool   `json:"privatePromoted"`
}

func (s *MyShowVlan) GetCmd() string {
        return "show vlan configured-ports"
}

func main() {
        node, err := goeapi.ConnectTo("dut")
        if err != nil {
                panic(err)
        }

        sv := &MyShowVlan{}

        handle, _ := node.GetHandle("json")
        handle.AddCommand(sv)
        if err := handle.Call(); err != nil {
                panic(err)
        }

        for k, v := range sv.Vlans {
                fmt.Printf("Vlan:%s\n", k)
                fmt.Printf("  Name  : %s\n", v.Name)
                fmt.Printf("  Status: %s\n", v.Status)
        }
}

It worked and I get output like:

Vlan:1012
  Name  : VLAN1012
  Status: active
Vlan:1013
  Name  : hack
  Status: active
Vlan:1
  Name  : default
  Status: active
Vlan:1010
  Name  : VLAN1010
  Status: active
Vlan:1011
  Name  : VLAN1011 
  Status: active

Now when I change e.g. a variable name in the Vlan struct like below:

type Vlan struct {
        Status     string               `json:"status"`
        Name1       string               `json:"name"`
        Interfaces map[string]Interface `json:"interfaces"`
        Dynamic    bool                 `json:"dynamic"`
}

I would expect that things work in the same way because there is a json tag. However, the output I get is:

Vlan:1012
  Name  : 
  Status: active
Vlan:1013
  Name  : 
  Status: active
Vlan:1
  Name  : 
  Status: active
Vlan:1010
  Name  : 
  Status: active
Vlan:1011
  Name  : 
  Status: active

For some reason the tag is ignored and it looks like it uses the variable name itself for the JSON Unmarshal instead of the tag information.

move to go modules

We currently are leveraging godep for dependency management. Should move to go modules instead.

Are only standard-typed ACLs valid?

Hi there,

I noticed that ACL functions under module/acl.go only returns standard-typed ACLs. One such example can be seen in https://github.com/aristanetworks/goeapi/blob/master/module/acl.go#L161 . Device configs aren't often like this and many can have extended ACLs without any specified type, i.e. ip access-list NAME instead of ip access-list standard NAME, or ip access-list payload NAME.

Is the goeapi code just lacking support for non-standard ACL types (and if this is the case, would you be interested in PR to fix this?), or are general-typed ACLs the only thing you'll consider to be valid?

cross complie issue with os/user

os/user relies on cgo...and it appears that cgo is disabled when cross compiling. When we try and get User (via. user.Current()) it returns error. We don't currently handle the error. Fix for this is to Getenv("HOME") if user.Current() fails.

String Interpolation with a custom command

@cheynearista

How do I implement a custom command where I pass a string to the GetCmd() function?

In my case below I want to build a command that returns only a single interface - testing with Ethernet1

type ShowInterfaceDescription struct {
	InterfaceDescriptions map[string]InterfaceInfo `json:"interfaceDescriptions"`
}

type InterfaceInfo struct {
	InterfaceStatus    string `json:"interfaceStatus"`
	Description        string `json:"description"`
	LineProtocolStatus string `json:"lineProtocolStatus"`
}

func (s *ShowInterfaceDescription) GetCmd() string {
	i := "Ethernet1"
	return fmt.Sprintf("show interfaces %v description", i)
}

Can't unmarshal -Infinity

When trying to unmarshall the output of show interfaces transceiver dom thresholds, you sometimes can come across a channel value with -Infinity. This causes the JSON decoder to fail with an error like this:

interface-diagnostics: invalid character 'I' in numeric literal"

The JSON object in question looks like this:

{
  "interfaces": {
    "Ethernet54/1": {
      "updateTime": 1628733658.605624,
      "displayName": "Ethernet54/1",
      "parameters": {
        "txPower": {
          "channels": {
            "1": -Infinity
          },
          "threshold": {},
          "unit": "dBm"
        },
        "temperature": {
          "channels": {
            "-": 29.703125
          },
          "threshold": {
            "highWarn": 70.0,
            "lowWarn": 0.0,
            "highAlarm": 75.0,
            "lowAlarm": -5.0
          },
          "unit": "C"
        },
        "txBias": {
          "channels": {
            "1": 0.0
          },
          "threshold": {
            "highWarn": 0.0,
            "lowWarn": 0.0,
            "highAlarm": 0.0,
            "lowAlarm": 0.0
          },
          "unit": "mA"
        },
        "rxPower": {
          "channels": {
            "1": -30.0
          },
          "threshold": {},
          "unit": "dBm"
        },
        "voltage": {
          "channels": {
            "-": 3.3201
          },
          "threshold": {
            "highWarn": 3.45,
            "lowWarn": 3.1500000000000004,
            "highAlarm": 3.8000000000000003,
            "lowAlarm": 2.8400000000000003
          },
          "unit": "V"
        }
      },
      "vendorSn": "XXXXXX",
      "mediaType": "40GBASE-SR4"
    }
}

It seems that -Infinity isn't valid JSON, so it isn't supported by the JSON parser. So what is the way to handle this so you can at least skip over this value and still get the rest of the valid data back?

Users module issue with SSHKey

Unable to obtain SSHKeys using the provided module Users.

I am trying to make use of the Arista goeapi (https://github.com/aristanetworks/goeapi)

While working with the module.Users, i see that the regular expressions used to parse the running config to build UserConfig uses sshkey 
Refer :
https://github.com/aristanetworks/goeapi/blob/master/module/users.go#L57
https://github.com/aristanetworks/goeapi/blob/master/module/users.go#L220
https://github.com/aristanetworks/goeapi/blob/master/module/users.go#L366-L374

I am using  EOS version 4.25.2Fand with this version, the sshkey is not a keyword that is seen in the running config or with the eos commands. While working with the EOS version 4.25 +. the regex should be using ssh-key instead of sshkey.
Looks like a change between Arista EOS versions that is not captured in code.

Sample from running config beyond EOS 4.25.x

username test-user ssh-key ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC6FMb/x8E8WJ4KOZjm4uMF23FoxA7KQuIxr7nsKUTEE6i9WjAxo1v3t5Nffu5Qc2iD783BCKQSr3A9QuZ141Tsqu3ufkKLyio//ufVBrgKNYfn3h+8+1DbdUt5gqcbb2eTpxuaUjSweCesjvCUqR+YwdfIj1DGfUOtDsu1FgZx+IUaYC/0rEETbnlTSvrtgG7GU1QbqRBGPYm02e+asdasYLCQfvpy86y6fk3coY72JuKNR6IEmKWl9Kcd/6RZiIHVAWAybYkrYsNGZKzqQm0l0rN0+yF5kXjyeImJWqaAHfnaFVJCpW7kEUT5CoTivlGyCKYWSfjwi/lSGdbWu1Vf87uvfHe5ojB0z9hragbRoRj2d6IzKiuGMoWuvq2QooUed0R58b5bBqMvI3xONiUKRaa/a4YxBUHjpj+xD6zKVgmge2hrnEf6sASIf36BRXqFIXIXNJgSgSvrIdQN0ut6lNbNrG+JF+7KkNCG0krxvZRp55A3CsF6MB/G+GEw1ccXU= test-user@mars

SwitchPortEntity.GetAll() is real dang slow

I'm trying to retrieve interface information from a device, and it's taking quite some amount of time. I see that the full config is first pulled, to match interface names, and then call out again for the full config to match only the interface we're after. Perhaps there is a way that this can be sped up. Or perhaps, is there another way that I can get at the state of the running device, config or otherwise.

Have Connect() return Node

Currently Connect() returns EapiConnectionEntity. Brought to attention that this would be be easier to use (for users not using ConnectTo()) if we return Node. Need to look into and address.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.