Skip to content

Commit

Permalink
Merge pull request #1 from hellt/single-device-mode
Browse files Browse the repository at this point in the history
single-device mode
  • Loading branch information
hellt authored Jun 7, 2021
2 parents 4316081 + d92f24e commit d423eb7
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 98 deletions.
16 changes: 1 addition & 15 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,6 @@ jobs:
go-version: ${{ env.GOVER }}
- run: go test -cover ./...

staticcheck:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: ${{ env.GOVER }}
- name: Staticcheck
run: |
go get -u honnef.co/go/tools/cmd/staticcheck
staticcheck ./...
lint:
runs-on: ubuntu-20.04
steps:
Expand All @@ -42,15 +30,13 @@ jobs:
uses: golangci/golangci-lint-action@v2
with:
version: v1.40.1
- name: golangci-lint
run: golangci-lint run

build-and-release:
runs-on: ubuntu-20.04
if: startsWith(github.ref, 'refs/tags/v')
needs:
- unit-test
- staticcheck
- lint
steps:
- name: Checkout
uses: actions/checkout@v2
Expand Down
27 changes: 2 additions & 25 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ linters-settings:
settings:
mnd:
# don't include the "operation" and "assign"
checks: argument,case,condition,return
checks: [argument, case, condition, return]
govet:
check-shadowing: true
settings:
Expand All @@ -43,7 +43,7 @@ linters-settings:
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
lll:
line-length: 100
line-length: 150
misspell:
locale: US
nolintlint:
Expand Down Expand Up @@ -110,29 +110,6 @@ issues:
- path: _test\.go
linters:
- gomnd
# ignoring launching subprocess with user provided args
- path: transport/system
text: "G204"
linters:
- gosec
# ignoring slightly too nested channel authentication
- path: channel/authenticate
text: "deeply nested"
linters:
- nestif
# ignoring complaint about global logger
- path: logging/logging
text: "global variable"
linters:
- gochecknoglobals
# ignoring complaint about junos/xr core drivers having dupl lines
- path: driver/core
text: "9-59 lines are duplicate"
linters:
- dupl
# - text: "TODO/BUG/FIXME"
# linters:
# - godox

run:
skip-dirs:
Expand Down
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lint:
docker run -it --rm -v $$(pwd):/app -w /app golangci/golangci-lint:v1.40.1 golangci-lint run -v
2 changes: 1 addition & 1 deletion commando/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/urfave/cli/v2"
)

// NewCLI defines the CLI flags and commands
// NewCLI defines the CLI flags and commands.
func NewCLI() *cli.App {
appC := &appCfg{}
flags := []cli.Flag{
Expand Down
108 changes: 66 additions & 42 deletions commando/cmdo.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,35 @@ import (
"gopkg.in/yaml.v2"
)

var version string
var commit string
var supportedPlatforms = []string{
"arista_eos",
`cisco_iosxr`,
`cisco_iosxe`,
`cisco_nxos`,
`juniper_junos`,
`nokia_sros`,
`nokia_sros_classic`,
`nokia_srlinux`,
}
var (
version string
commit string //nolint:gochecknoglobals
supportedPlatforms = []string{ //nolint:gochecknoglobals
"arista_eos",
`cisco_iosxr`,
`cisco_iosxe`,
`cisco_nxos`,
`juniper_junos`,
`nokia_sros`,
`nokia_sros_classic`,
`nokia_srlinux`,
}

errNoDevices = errors.New("no devices to send commands to")
errNoPlatformDefined = fmt.Errorf("platform is not set, use --platform | -k <platform> to set one of the supported platforms: %q",
supportedPlatforms)
errNoUsernameDefined = errors.New("username was not provided. Use --username | -u to set it")
errNoPasswordDefined = errors.New("password was not provided. Use --passoword | -p to set it")
errNoCommandsDefined = errors.New("commands were not provided. Use --commands | -c to set a `::` delimited list of commands to run")
)

const (
fileOutput = "file"
stdoutOutput = "stdout"
)

type inventory struct {
Devices map[string]device `yaml:"devices,omitempty"`
Devices map[string]*device `yaml:"devices,omitempty"`
}

type device struct {
Expand All @@ -57,12 +71,11 @@ type appCfg struct {
commands string // commands to send
}

// run runs the commando
// run runs the commando.
func (app *appCfg) run() error {
// logging.SetDebugLogger(log.Print)
i := &inventory{}
// start bulk commands routine
if len(app.address) == 0 {
if app.address == "" {
if err := app.loadInventoryFromYAML(i); err != nil {
return err
}
Expand All @@ -72,38 +85,39 @@ func (app *appCfg) run() error {
}
}

rw, err := app.newResponseWriter(app.output)
if err != nil {
return err
}

rw := app.newResponseWriter(app.output)
rCh := make(chan *base.MultiResponse)

if app.output == "file" {
if app.output == fileOutput {
log.SetOutput(os.Stderr)
log.Infof("Started sending commands and capturing outputs...")
}

wg := &sync.WaitGroup{}
wg.Add(len(i.Devices))

for n, d := range i.Devices {
go app.runCommands(wg, n, d, rCh)
go app.runCommands(n, d, rCh)

resp := <-rCh
go app.outputResult(wg, rw, n, d, resp)
}

wg.Wait()

if app.output == "file" {
if app.output == fileOutput {
log.Infof("outputs have been saved to '%s' directory", app.outDir)
}

return nil
}

func (app *appCfg) runCommands(wg *sync.WaitGroup, name string, d device, rCh chan<- *base.MultiResponse) {
func (app *appCfg) runCommands(
name string,
d *device,
rCh chan<- *base.MultiResponse) {
var driver *network.Driver

var err error

switch d.Platform {
Expand Down Expand Up @@ -144,17 +158,24 @@ func (app *appCfg) runCommands(wg *sync.WaitGroup, name string, d device, rCh ch
}

rCh <- r

}

func (app *appCfg) outputResult(wg *sync.WaitGroup, rw responseWriter, name string, d device, r *base.MultiResponse) {
func (app *appCfg) outputResult(
wg *sync.WaitGroup,
rw responseWriter,
name string,
d *device,
r *base.MultiResponse) {
defer wg.Done()
rw.WriteResponse(r, name, d, app)

if err := rw.WriteResponse(r, name, d, app); err != nil {
log.Errorf("error while writing the response: %v", err)
}
}

// filterDevices will remove the devices which names do not match the passed filter
// filterDevices will remove the devices which names do not match the passed filter.
func filterDevices(i *inventory, f string) {
if len(f) == 0 {
if f == "" {
return
}

Expand All @@ -179,33 +200,36 @@ func (app *appCfg) loadInventoryFromYAML(i *inventory) error {
}

filterDevices(i, app.devFilter)

if len(i.Devices) == 0 {
return errors.New("no devices to send commands to")
return errNoDevices
}

return nil
}

func (app *appCfg) loadInventoryFromFlags(i *inventory) error {

if len(app.platform) == 0 {
return fmt.Errorf("platform is not set, use --platform | -k <platform> to set one of the supported platforms: %q", supportedPlatforms)
if app.platform == "" {
return errNoPlatformDefined
}
if len(app.username) == 0 {
return errors.New("username was not provided. Use --username | -u to set it")

if app.username == "" {
return errNoUsernameDefined
}
if len(app.password) == 0 {
return errors.New("password was not provided. Use --passoword | -p to set it")

if app.password == "" {
return errNoPasswordDefined
}
if len(app.commands) == 0 {
return errors.New("commands were not provided. Use --commands | -c to set a `::` delimited list of commands to run")

if app.commands == "" {
return errNoCommandsDefined
}

cmds := strings.Split(app.commands, "::")

i.Devices = map[string]device{}
i.Devices = map[string]*device{}

i.Devices[app.address] = device{
i.Devices[app.address] = &device{
Platform: app.platform,
Address: app.address,
Username: app.username,
Expand Down
37 changes: 22 additions & 15 deletions commando/respwriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,62 +13,69 @@ import (
)

type responseWriter interface {
WriteResponse(r *base.MultiResponse, name string, d device, appCfg *appCfg) error
WriteResponse(r *base.MultiResponse, name string, d *device, appCfg *appCfg) error
}

func (app *appCfg) newResponseWriter(f string) (responseWriter, error) {
func (app *appCfg) newResponseWriter(f string) responseWriter {
switch f {
case "file":
case fileOutput:
parentDir := "outputs"
switch app.timestamp {
case true:
if app.timestamp {
parentDir = parentDir + "_" + time.Now().Format(time.RFC3339)
}

app.outDir = parentDir

return &fileWriter{
parentDir,
}, nil
case "stdout":
return &consoleWriter{}, nil
}
case stdoutOutput:
return &consoleWriter{}
}

return nil, nil
return nil
}

// consoleWriter writes the scrapli responses to the console
// consoleWriter writes the scrapli responses to the console.
type consoleWriter struct{}

func (w *consoleWriter) WriteResponse(r *base.MultiResponse, name string, d device, appCfg *appCfg) error {
func (w *consoleWriter) WriteResponse(r *base.MultiResponse, name string, d *device, appCfg *appCfg) error {
color.Green("\n**************************\n%s\n**************************\n", name)

for idx, cmd := range d.SendCommands {
c := color.New(color.Bold)
c.Printf("\n-- %s:\n", cmd)

if r.Responses[idx].Failed {
color.Set(color.FgRed)
}

fmt.Println(r.Responses[idx].Result)
}

return nil
}

// fileWriter writes the scrapli responses to the files on disk
// fileWriter writes the scrapli responses to the files on disk.
type fileWriter struct {
dir string // output dir name
}

func (w *fileWriter) WriteResponse(r *base.MultiResponse, name string, d device, appCfg *appCfg) error {

func (w *fileWriter) WriteResponse(r *base.MultiResponse, name string, d *device, appCfg *appCfg) error {
outDir := path.Join(w.dir, name)
if err := os.MkdirAll(outDir, 0755); err != nil {
return err
}

for idx, cmd := range d.SendCommands {
c := sanitizeCmd(cmd)

rb := []byte(r.Responses[idx].Result)
if err := ioutil.WriteFile(path.Join(outDir, c), rb, 0755); err != nil {
if err := ioutil.WriteFile(path.Join(outDir, c), rb, 0755); err != nil { //nolint:gosec
return err
}
}

return nil
}

Expand Down

0 comments on commit d423eb7

Please sign in to comment.