Goeapi Summary

All of the examples should be found within the repo on my personal github here

Goeapi is the JSON rpc wrapper for Go. It is a Go module where a client can be created to interact with a Arista EOS device to send commands or configuration. In my opinion it is actually incredibly easy. This is the Go alternative to the really popular pyeapi I generally like Goapi because well, it is go based :D.

I am doing a Nanog 90 talk on Go for network engineers and put a few slight demos together for connecting to a device weather its on Go eapi or gNMI I could not find much for blog posts that really explain and give some examples on Goeapi.

JSON RPC’s

So pretty much every major network vendor offers a JSON rpc for network operating systems. JSON RPC under the covers simply makes it feel like the client is accessing the switch like it is directly on the switch and accessing underlying system CLI. In the Arista case it is the CLI agent. So any bit of CLI can be passed into the RPC like it was done under the CLI.

The JSON RPC API can be accessed if it is turned on for port 443/80.

Simple configuration.

management api http-commands
   no shutdown

This will turn on HTTPS JSON RPC.

Simple verification

switch1#show management api http-commands
Enabled:            Yes
HTTPS server:       running, set to use port 443

Our goal here is to simply connect over the rpc framework and execute commands against the API.

Go code

The first portion is structuring the data the way you would like. When I say structuring the data we need to do things like have the HTTPS port, username , password etc. For each of these they need to be of a certain type(string,int etc)

So we create a struct called Conn outside of the main package.

type Conn struct {
	Transport string
	Host      string
	Username  string
	Password  string
	Port      int
	Config    string
}

Evertime we leverage Conn struct we would then want to use the goeapi’s node type.

func (c *Conn) Connect() (*goeapi.Node, error) {
	connect, err := goeapi.Connect(c.Transport, c.Host, c.Username, c.Password, c.Port)
	if err != nil {
		fmt.Println(err)
	}
	return connect, nil
}

The Connect() method of the Conn struct will simply connect to a Arista EOS device and return a pointer to goeapis Node type. Which has a lot of methods under it like RunningConnfig and RunCommands which in typical go ideaology makes things pretty simplistic here for the operator to have dotted notation or typing to get to these methods once the Node type is returned from the Connect() method.

Inside of main function we do a few things here.

Initialize a struct with Conn. This is where we have structured out credential and other connection parameters to the device.

d := Conn{
		Transport: "https",
		Host:      "10.255.111.161",
		Username:  "cvpadmin",
		Password:  "cvp123!",
		Port:      443,
	}

Initialize the Connect method from what is now d.

Connect, err := d.Connect()
	if err != nil {
		fmt.Println(err)
	}

Since d.Connect() retuns a pointer to goeapi.Node type we now have access to all the *Node types methods like runningconfig, runcommands and so on.

We can now get the runningconfig rather easily.

RunningConfig := Connect.RunningConfig()
	fmt.Println(RunningConfig + "\n")

A simply go run main.go will then return the running-config as a string.

Truncated Result

! Command: show running-config all
! device: ceos1 (cEOSLab, EOS-4.31.0F-33797590.4310F (engineering build))
!
no enable password
no aaa root
no aaa authentication policy local allow-nopassword-remote-login
no username root ssh principal
no aaa authorization policy local default-role
!

Next we want to blast away some commands at the device and when it returns the result we want to unmarshall it in Go terms.

Since d.Connect() returns all the goeapi.Node types we have access to enable enable takes in a set of commands in the form of in go []string and returns a map[string]string. So the first order here is to create a []string{} of commands we want to execute against the device.

commands := []string{"show version"}
	conf, err := Connect.Enable(commands)
	if err != nil {
		panic(err)
	}

Now conf will be the return of show version which will be a map[string]string. We simply need to loop through the map to get the data we want.

for k, v := range conf[0] {
		fmt.Println(k, v)
	}
	fmt.Print(conf[0])

The reson we are using the first element ie conf[0] is I believe eapi returns result as a element first.

Result

command show version
result Arista vEOS-lab
Hardware version:
Serial number: SN-DC1-SPINE1
Hardware MAC address: c4b6.ba55.276a
System MAC address: c4b6.ba55.276a

So this is nice because we can print out for every element in the response. This is good for humans but now we want typed access for each line this will make more sense in the next example.

Create a struct for the response data. Since the response data we can view what we know it looks like by simply going to the device on port :8080 and issuing a show version. Here is the json response.

1

We can see for example “modelName”: “cEOSLab” is a json element we would be interested in. This is a element we can key upon if we structured our data in typical go fashion. So lets create a response struct for this.

type VersionResp struct {
	ModelName        string  `json:"modelName"`
	InternalVersion  string  `json:"internalVersion"`
	SystemMacAddress string  `json:"systemMacAddress"`
	SerialNumber     string  `json:"serialNumber"`
	MemTotal         int     `json:"memTotal"`
	BootupTimestamp  float64 `json:"bootupTimestamp"`
	MemFree          int     `json:"memFree"`
	Version          string  `json:"version"`
	Architecture     string  `json:"architecture"`
	InternalBuildID  string  `json:"internalBuildId"`
	HardwareRevision string  `json:"hardwareRevision,omitempty"`
}

The VersionResp struct matches the goeapi response. We now need to send a request and have it unmarshall with the VersionResp struct.

func (s *VersionResp) GetCmd() string {
	return "show version"
}

Showversion := &VersionResp{}

This code does two things. Initializes the VersionResp struct and also creates a method to simply pass in the CLI of show version.

	// Cal the GetHandle method
	handle, err := Connect.GetHandle("json")
	if err != nil {
		fmt.Println(err)
	}
	// This will add to a new slice of AddCommands to send to the switch.
	handle.AddCommand(Showversion)
	// If it exists handle.Call will append all the AddCommands and then connect to the switch
	if err := handle.Call(); err != nil {
		panic(err)
	}

Next we call the GetHandle function which returns EapiReqHandle which has another method called AddCommand. The handle.AddCommand method can be called multiple times in the event that you need to send multiple commands over to the switch.

Once all the handle.AddCommands are added its simply appeneded to a slice and the Call method will execute every command.

By the time this is done we can then have typed access for every element.

	fmt.Printf("Version           : %s\n", Showversion.Version)
	fmt.Printf("System MAC        : %s\n", Showversion.SystemMacAddress)
	fmt.Printf("Serial Number     : %s\n", Showversion.SerialNumber)

Result

Version           : 4.31.0F-33797590.4310F (engineering build)
System MAC        : 00:1c:73:9f:05:5a
Serial Number     : 37D70B6246A5528A2942A849FA6D156D