From 33cc805b5aaf2ffdf0125b3362afd9230f0dfe18 Mon Sep 17 00:00:00 2001 From: Damian ONeill Date: Thu, 14 Nov 2019 10:06:34 +0000 Subject: [PATCH] Initial commit --- .github/workflows/go.yml | 26 ++ .github/workflows/release.yml | 27 ++ .gitignore | 21 ++ .goreleaser.yml | 44 +++ CONTRIBUTING.md | 64 ++++ LICENSE | 202 ++++++++++ Makefile | 15 + README.md | 38 ++ cmd/completion.go | 64 ++++ cmd/configure.go | 19 + cmd/configure_devices.go | 113 ++++++ cmd/docs.go | 31 ++ cmd/extract.go | 20 + cmd/extract_device_groups.go | 26 ++ cmd/extract_devices.go | 48 +++ cmd/extract_devices_test.go | 76 ++++ cmd/load.go | 23 ++ cmd/load_device_groups.go | 25 ++ cmd/load_devices.go | 73 ++++ cmd/load_devices_test.go | 58 +++ cmd/load_helper_files.go | 85 +++++ cmd/load_helper_files_test.go | 58 +++ cmd/root.go | 114 ++++++ cmd/summarise.go | 19 + cmd/summarise_installation.go | 122 ++++++ cmd/summarise_installation_test.go | 269 +++++++++++++ cmd/testdata/device-facts.json | 123 ++++++ cmd/transform.go | 32 ++ cmd/transform_devices.go | 88 +++++ cmd/version.go | 27 ++ cmd/version_test.go | 37 ++ docs/h7t.md | 33 ++ docs/h7t_completion.md | 57 +++ docs/h7t_configure.md | 34 ++ docs/h7t_configure_devices.md | 59 +++ docs/h7t_docs.md | 33 ++ docs/h7t_extract.md | 36 ++ docs/h7t_extract_device-groups.md | 34 ++ docs/h7t_extract_devices.md | 34 ++ docs/h7t_load.md | 40 ++ docs/h7t_load_device-groups.md | 35 ++ docs/h7t_load_devices.md | 35 ++ docs/h7t_load_helper-files.md | 35 ++ docs/h7t_summarise.md | 34 ++ docs/h7t_summarise_installation.md | 33 ++ docs/h7t_transform.md | 39 ++ docs/h7t_transform_devices.md | 36 ++ docs/h7t_version.md | 33 ++ dsl/deviceFacts.go | 45 +++ dsl/deviceGroups.go | 73 ++++ dsl/devices.go | 110 ++++++ dsl/systemDetails.go | 27 ++ dsl/testdata/devices.yml | 33 ++ dsl/things.go | 119 ++++++ dsl/things_test.go | 292 ++++++++++++++ go.mod | 26 ++ go.sum | 587 +++++++++++++++++++++++++++++ main.go | 13 + plugins/README.md | 34 ++ plugins/csv/main.go | 82 ++++ plugins/csv/main_test.go | 55 +++ plugins/transformer_interface.go | 56 +++ tools.go | 17 + 63 files changed, 4196 insertions(+) create mode 100644 .github/workflows/go.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .goreleaser.yml create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 cmd/completion.go create mode 100644 cmd/configure.go create mode 100644 cmd/configure_devices.go create mode 100644 cmd/docs.go create mode 100644 cmd/extract.go create mode 100644 cmd/extract_device_groups.go create mode 100644 cmd/extract_devices.go create mode 100644 cmd/extract_devices_test.go create mode 100644 cmd/load.go create mode 100644 cmd/load_device_groups.go create mode 100644 cmd/load_devices.go create mode 100644 cmd/load_devices_test.go create mode 100644 cmd/load_helper_files.go create mode 100644 cmd/load_helper_files_test.go create mode 100644 cmd/root.go create mode 100644 cmd/summarise.go create mode 100644 cmd/summarise_installation.go create mode 100644 cmd/summarise_installation_test.go create mode 100644 cmd/testdata/device-facts.json create mode 100644 cmd/transform.go create mode 100644 cmd/transform_devices.go create mode 100644 cmd/version.go create mode 100644 cmd/version_test.go create mode 100644 docs/h7t.md create mode 100644 docs/h7t_completion.md create mode 100644 docs/h7t_configure.md create mode 100644 docs/h7t_configure_devices.md create mode 100644 docs/h7t_docs.md create mode 100644 docs/h7t_extract.md create mode 100644 docs/h7t_extract_device-groups.md create mode 100644 docs/h7t_extract_devices.md create mode 100644 docs/h7t_load.md create mode 100644 docs/h7t_load_device-groups.md create mode 100644 docs/h7t_load_devices.md create mode 100644 docs/h7t_load_helper-files.md create mode 100644 docs/h7t_summarise.md create mode 100644 docs/h7t_summarise_installation.md create mode 100644 docs/h7t_transform.md create mode 100644 docs/h7t_transform_devices.md create mode 100644 docs/h7t_version.md create mode 100644 dsl/deviceFacts.go create mode 100644 dsl/deviceGroups.go create mode 100644 dsl/devices.go create mode 100644 dsl/systemDetails.go create mode 100644 dsl/testdata/devices.yml create mode 100644 dsl/things.go create mode 100644 dsl/things_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 plugins/README.md create mode 100644 plugins/csv/main.go create mode 100644 plugins/csv/main_test.go create mode 100644 plugins/transformer_interface.go create mode 100644 tools.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..52474c2 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,26 @@ +name: Go +on: [push] +jobs: + build: + name: Test + runs-on: ubuntu-latest + steps: + - name: Set up Go 1.13 + uses: actions/setup-go@v1 + with: + go-version: 1.13 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v1 + + - name: Get dependencies + run: | + go get -v -t -d ./... + if [ -f Gopkg.toml ]; then + curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh + dep ensure + fi + + - name: Test + run: go test -v ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..453034c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,27 @@ +name: Release +on: + push: + branches: + - "!*" + tags: + - "v*.*.*" +jobs: + build: + runs-on: ubuntu-latest + name: goreleaser + steps: + - name: Set up Go 1.13 + uses: actions/setup-go@v1 + with: + go-version: 1.13 + id: go + - name: Check out code into the Go module directory + uses: actions/checkout@v1 + - name: Check GITHUB_REF environment variable value + run: echo $GITHUB_REF + - name: Release via goreleaser + uses: goreleaser/goreleaser-action@master + with: + args: release + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..66dc233 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +dist +h7t +.idea +/plugins/csv/transformer +.testCoverage.txt \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..267ddf7 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,44 @@ +before: + hooks: + # you may remove this if you don't use vgo + - go mod tidy + - go fmt ./... +builds: + - id: "h7t" + main: ./main.go + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - id: "plugin-csv" + main: ./plugins/csv/main.go + binary: /plugins/csv/transformer + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + - windows + goarch: + - amd64 +archives: + - replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 +checksum: + name_template: "checksums.txt" +snapshot: + name_template: "{{ .Tag }}-next" +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..09d7142 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,64 @@ +# Contributing to h7t + +## Setup + +Assuming you have a go setup on your host, run make, which will trigger a download of the tools required to write code in the repo. + +## Command Structure + +In h7t commands represent actions/verbs, args are things/nouns/objects and Flags are modifiers/adjective (property/state) for actions. + +For example: + +| action | thing/object | property/state | +| ---------- | ----------------------------------- | -------------- | +| myapp verb | noun | --adjective | +| git clone | git@github.com:damianoneill/h7t.git | --bare | +| go get -u | github.com/aws/aws-sdk-go/... | | + +If adding new commands, understand how they will fit into the bigger picture by reviewing below. + +### Actions + +The following are the list of actions supported / planned for h7t + +- extract +- transform +- load +- summarise +- configure + +## Things + +The following are the list objects supported / planned by h7t, this includes Healthbot DSL objects + others for e.g. Junos Devices + +- device +- devices +- device-groups +- helper-files +- installation + +### States + +The following are the list of properties supported / planned by h7t + +#### Common + +- verbose (v) +- config +- help (h) +- authority (a) +- username (u) +- password (p) + +#### Action specific + +- N/A + +#### Thing specific + +- output_directory (o) +- input_directory (i) +- erase (e) +- plugin +- netconf-rpc (f) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..32dc408 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +all: commit +commit: + go test ./... + golangci-lint run + golint ./... + go mod tidy + go install && h7t docs + go build -o ./plugins/csv/transformer ./plugins/csv/main.go +tools: download + cat tools.go | grep _ | awk -F'"' '{print $$2'} | xargs -tI % go install % +download: + go mod download +build: + go install + go build -o ./plugins/csv/transformer ./plugins/csv/main.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..6beb206 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# h~~ealthbot~~7t + +[![GitHub release](https://img.shields.io/github/v/release/damianoneill/h7t.svg)](https://GitHub.com/damianoneill/h7t/releases/) +[![Go Report Card](https://goreportcard.com/badge/damianoneill/h7t)](http://goreportcard.com/report/damianoneill/h7t) +[![license](https://img.shields.io/github/license/damianoneill/h7t.svg)](https://github.com/damianoneill/h7t/blob/master/LICENSE) + +A command line tool for interacting with [Juniper Healthbot](https://www.juniper.net/us/en/products-services/sdn/contrail/contrail-healthbot/). + +## Synopsis + +A tool for interacting with Healthbot over the REST API. + +The initial focus of this tool is to provide bulk or aggregate functions, that simplify interacting with Healthbot. The initial use case is Extract Transform and Load (ETL) based. + +## Commands + +```console +h7t +├── configure +│   └── devices +├── extract +│   ├── device-groups +│   └── devices +├── load +│   ├── device-groups +│   ├── devices +│   └── helper-files +├── summarise +│   └── installation +└── transform + └── devices +``` + +A full list of the commands and their options is described in the [docs](./docs/h7t.md). + +## Transforms + +The tool includes a plugin based solution for transforming customer data into a format that can be consumed. Further information is available in the [plugins](./plugins/) directory. diff --git a/cmd/completion.go b/cmd/completion.go new file mode 100644 index 0000000..f19c95c --- /dev/null +++ b/cmd/completion.go @@ -0,0 +1,64 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var completionTarget string + +// completionCmd represents the completion command +var completionCmd = &cobra.Command{ + Use: "completion", + Short: "Generate shell completion script for " + rootCmd.Use, + Long: `Generates a shell completion script for ` + rootCmd.Use + `. + NOTE: The current version supports Bash only. + This should work for *nix systems with Bash installed. + + By default, the file is written directly to /etc/bash_completion.d + for convenience, and the command may need superuser rights, e.g.: + + $ sudo ` + rootCmd.Use + ` completion + + Add ` + "`--completionfile=/path/to/file`" + ` flag to set alternative + file-path and name. + + For e.g. on OSX with bash completion installed with brew you should + + $ ` + rootCmd.Use + ` completion --completionfile $(brew --prefix)/etc/bash_completion.d/` + rootCmd.Use + `.sh + + Logout and in again to reload the completion scripts, + or just source them directly: + + $ . /etc/bash_completion + + or using if using brew + + $ . $(brew --prefix)/etc/bash_completion`, + + Run: Completion, +} + +// Completion is a helper function to allow passing arguments to +// other functions (so that they can be unit tested) +func Completion(cmd *cobra.Command, args []string) { + err := cmd.Root().GenBashCompletionFile(completionTarget) + completion(err, args...) +} + +func completion(err error, args ...string) { + if err != nil { + fmt.Println(err) + return + } + fmt.Println("Bash completion file for "+rootCmd.Use+" saved to", completionTarget) +} + +func init() { + rootCmd.AddCommand(completionCmd) + + completionCmd.PersistentFlags().StringVarP(&completionTarget, "completionfile", "", "/etc/bash_completion.d/"+rootCmd.Use+".sh", "completion file") + // Required for bash-completion + _ = completionCmd.PersistentFlags().SetAnnotation("completionfile", cobra.BashCompFilenameExt, []string{}) // nolint : gosec +} diff --git a/cmd/configure.go b/cmd/configure.go new file mode 100644 index 0000000..11b5a02 --- /dev/null +++ b/cmd/configure.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// configureCmd represents the configure command +var configureCmd = &cobra.Command{ + Use: "configure", + Short: "Configure information relating to a Healthbot Installation", + Long: `Configure components involved in a Healthbot installation e.g. Devices.`, + Run: func(cmd *cobra.Command, args []string) { + _ = cmd.Help() + }, +} + +func init() { + rootCmd.AddCommand(configureCmd) +} diff --git a/cmd/configure_devices.go b/cmd/configure_devices.go new file mode 100644 index 0000000..0142e0d --- /dev/null +++ b/cmd/configure_devices.go @@ -0,0 +1,113 @@ +package cmd + +import ( + "context" + "fmt" + "os" + + "github.com/damianoneill/h7t/dsl" + "github.com/damianoneill/net/netconf" + "github.com/spf13/afero" + "github.com/spf13/cobra" + "golang.org/x/crypto/ssh" +) + +var verbose string + +func sendRPC(d dsl.Device, b []byte) (err error) { + + sshConfig := &ssh.ClientConfig{ + User: *d.Password.Username, + Auth: []ssh.AuthMethod{ssh.Password(*d.Password.Password)}, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + port := 830 + if d.IAgent != nil { + port = d.IAgent.Port + } + + serverAddress := fmt.Sprintf("%v:%d", d.Host, port) + s, err := netconf.NewRPCSession(context.Background(), sshConfig, serverAddress) + if err != nil { + return + } + reply, err := s.Execute(netconf.Request(string(b))) + s.Close() + + if verbose == "true" { + fmt.Fprintf(os.Stdout, "rpc response: %v", reply.Data) + } + + return +} + +func applyNetconfRPC(devicesFiles []string, rpcFilename string) (err error) { + rpcFile, err := afero.ReadFile(AppFs, rpcFilename) + if err != nil { + return + } + for _, device := range devicesFiles { + f, deviceErr := afero.ReadFile(AppFs, device) + if deviceErr != nil { + return + } + devices := dsl.Devices{} + err = devices.Unmarshal(f) + if err != nil { + return + } + for _, d := range devices.Device { + rpcError := sendRPC(d, rpcFile) + fmt.Fprintf(os.Stdout, "Problem with configuring Device %v: %v", d.DeviceID, rpcError) + // do not error out, could be device is gone, continue trying others + } + } + return +} + +// devicesCmd represents the devices command +var devicesCmd = &cobra.Command{ + Use: "devices", + Short: "Load Devices with configuration", + Long: `Load into Devices, configuration defined in the netconf rpc file. + +E.g. + +$ cat sample.rpc + + + + + + + + + + + + + + + + + + + +`, + RunE: func(cmd *cobra.Command, args []string) (err error) { + verbose = cmd.Flag("verbose").Value.String() + devices, err := getDirectoryContents(cmd.Flag("input_directory").Value.String()) + if err != nil { + return + } + err = applyNetconfRPC(devices, cmd.Flag("netconf_rpc").Value.String()) + return + }, +} + +func init() { + configureCmd.AddCommand(devicesCmd) + devicesCmd.PersistentFlags().StringP("input_directory", "i", ".", "directory where the device configuration will be loaded from") + devicesCmd.PersistentFlags().StringP("netconf_rpc", "f", "", "file that contains a netconf rpc") +} diff --git a/cmd/docs.go b/cmd/docs.go new file mode 100644 index 0000000..c35a1cd --- /dev/null +++ b/cmd/docs.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "fmt" + "log" + + "github.com/spf13/cobra" + "github.com/spf13/cobra/doc" +) + +func generateMarkdown() { + err := doc.GenMarkdownTree(rootCmd, "./docs/") + if err != nil { + log.Fatal(err) + } +} + +// docsCmd represents the docs command +var docsCmd = &cobra.Command{ + Use: "docs", + Short: "Generate Markdown for the commands in " + rootCmd.Use, + Long: `For ` + rootCmd.Use + ` generate Markdown Documents for each of the commands and write them to a folder named ./docs`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Writing command descriptions to ./docs") + generateMarkdown() + }, +} + +func init() { + rootCmd.AddCommand(docsCmd) +} diff --git a/cmd/extract.go b/cmd/extract.go new file mode 100644 index 0000000..a7b6ee0 --- /dev/null +++ b/cmd/extract.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// extractCmd represents the extract command +var extractCmd = &cobra.Command{ + Use: "extract", + Short: "Extract information from a Healthbot Installation", + Long: `Extract dsl from Healthbot for e.g. Devices, Device Groups, etc.`, + Run: func(cmd *cobra.Command, args []string) { + _ = cmd.Help() + }, +} + +func init() { + rootCmd.AddCommand(extractCmd) + extractCmd.PersistentFlags().StringP("output_directory", "o", ".", "directory where the configuration will be stored") +} diff --git a/cmd/extract_device_groups.go b/cmd/extract_device_groups.go new file mode 100644 index 0000000..c7bd75a --- /dev/null +++ b/cmd/extract_device_groups.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "github.com/damianoneill/h7t/dsl" + "github.com/spf13/cobra" + "gopkg.in/resty.v1" +) + +// extractDeviceGroupsCmd represents the extract DeviceGroups command +var extractDeviceGroupsCmd = &cobra.Command{ + Use: "device-groups", + Short: "Extract Device Groups configuration", + Long: `Collect from a Healthbot installation the configuration for the Devices Groups.`, + RunE: func(cmd *cobra.Command, args []string) (err error) { + dg := dsl.DeviceGroups{} + err = dsl.ExtractThingFromResource(resty.DefaultClient, &dg, ci) + if err != nil { + return + } + return WriteThingsToFile(&dg, cmd.Flag("output_directory").Value.String()+filePathSeperator+"device-groups.yml") + }, +} + +func init() { + extractCmd.AddCommand(extractDeviceGroupsCmd) +} diff --git a/cmd/extract_devices.go b/cmd/extract_devices.go new file mode 100644 index 0000000..fba1300 --- /dev/null +++ b/cmd/extract_devices.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "errors" + "fmt" + "os" + + "github.com/damianoneill/h7t/dsl" + "github.com/spf13/cobra" + "gopkg.in/resty.v1" +) + +// WriteThingsToFile - common function used by commands creating yaml things +func WriteThingsToFile(thing dsl.Thing, namedfile string) (err error) { + if thing.Count() == 0 { + return errors.New("Zero Things, not writing to file") + } + f, err := AppFs.Create(namedfile) + if err != nil { + return + } + defer f.Close() + err = dsl.WriteThingToFile(thing, f) + if err != nil { + return + } + fmt.Fprintf(os.Stdout, "Wrote %v Things to %v \n", thing.Count(), namedfile) + return f.Sync() // https://www.joeshaw.org/dont-defer-close-on-writable-files/#update-2 +} + +// extractDevicesCmd represents the devices command +var extractDevicesCmd = &cobra.Command{ + Use: "devices", + Short: "Extract Device configuration", + Long: `Collect from a Healthbot installation the configuration for the Devices.`, + RunE: func(cmd *cobra.Command, args []string) (err error) { + devices := dsl.Devices{} + err = dsl.ExtractThingFromResource(resty.DefaultClient, &devices, ci) + if err != nil { + return + } + return WriteThingsToFile(&devices, cmd.Flag("output_directory").Value.String()+filePathSeperator+"devices.yml") + }, +} + +func init() { + extractCmd.AddCommand(extractDevicesCmd) +} diff --git a/cmd/extract_devices_test.go b/cmd/extract_devices_test.go new file mode 100644 index 0000000..a6e10c9 --- /dev/null +++ b/cmd/extract_devices_test.go @@ -0,0 +1,76 @@ +package cmd + +import ( + "testing" + + "github.com/tj/assert" + + "github.com/spf13/afero" + + "github.com/damianoneill/h7t/dsl" +) + +func TestWriteDevicesToFile(t *testing.T) { + + AppFs = afero.NewMemMapFs() + + type args struct { + thing dsl.Thing + namedfile string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "No devices should produce an error", + args: args{ + thing: &dsl.Devices{ + Device: nil, + }, + namedfile: "doesnt matter", + }, + wantErr: true, + }, + { + name: "Valid devices should produce an file", + args: args{ + thing: &dsl.Devices{ + Device: []dsl.Device{dsl.Device{ + DeviceID: "mx1", + Host: "10.0.0.1", + }}, + }, + namedfile: "devices.yml", + }, + wantErr: false, + }, + { + name: "Valid device-groups should produce an file", + args: args{ + thing: &dsl.DeviceGroups{ + DeviceGroup: []dsl.DeviceGroup{{ + DeviceGroupName: "dg1", + }}, + }, + namedfile: "./device-groups.yml", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := WriteThingsToFile(tt.args.thing, tt.args.namedfile); (err != nil) != tt.wantErr { + t.Errorf("WriteThingsToFile() error = %v, wantErr %v", err, tt.wantErr) + } + + if !tt.wantErr { + exists, existErr := afero.Exists(AppFs, tt.args.namedfile) + assert.Nil(t, existErr, "Call to exist should not return an error") + assert.True(t, exists, "File should exist") + } + + }) + } +} diff --git a/cmd/load.go b/cmd/load.go new file mode 100644 index 0000000..a9a1b07 --- /dev/null +++ b/cmd/load.go @@ -0,0 +1,23 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// loadCmd represents the load command +var loadCmd = &cobra.Command{ + Use: "load", + Short: "Load information into a Healthbot Installation", + Long: `Load dsl into Healthbot for e.g. Devices, Device Groups, etc.. + +Load sub-commands work by iterating over all files in the input directory`, + Run: func(cmd *cobra.Command, args []string) { + _ = cmd.Help() + }, +} + +func init() { + rootCmd.AddCommand(loadCmd) + loadCmd.PersistentFlags().StringP("input_directory", "i", ".", "directory where the configuration will be loaded from") + loadCmd.PersistentFlags().BoolP("erase", "e", false, "erase the thing(s) identified in configuration") +} diff --git a/cmd/load_device_groups.go b/cmd/load_device_groups.go new file mode 100644 index 0000000..d4362f2 --- /dev/null +++ b/cmd/load_device_groups.go @@ -0,0 +1,25 @@ +package cmd + +import ( + "github.com/damianoneill/h7t/dsl" + "github.com/spf13/cobra" +) + +// loadDeviceGroupsCmd represents the load Device Group command +var loadDeviceGroupsCmd = &cobra.Command{ + Use: "device-groups", + Short: "Load Device Groups configuration", + Long: `Load into a Healthbot installation the configuration for the Device Groups.`, + RunE: func(cmd *cobra.Command, args []string) (err error) { + files, err := getDirectoryContents(cmd.Flag("input_directory").Value.String()) + if err != nil { + return + } + err = loadThings(&dsl.DeviceGroups{}, files, cmd.Flag("erase").Value.String()) + return + }, +} + +func init() { + loadCmd.AddCommand(loadDeviceGroupsCmd) +} diff --git a/cmd/load_devices.go b/cmd/load_devices.go new file mode 100644 index 0000000..2eeabdc --- /dev/null +++ b/cmd/load_devices.go @@ -0,0 +1,73 @@ +package cmd + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/damianoneill/h7t/dsl" + "github.com/spf13/afero" + "github.com/spf13/cobra" + "gopkg.in/resty.v1" +) + +func getDirectoryContents(inputDirectory string) (matches []string, err error) { + matches, err = afero.Glob(AppFs, inputDirectory+filePathSeperator+"*") + if err != nil { + return + } + tmp := matches[:0] + for _, match := range matches { + isDir, direrr := afero.IsDir(AppFs, match) + if direrr != nil { + return + } + if !isDir { + tmp = append(tmp, match) + } + } + return tmp, err +} + +func loadThings(thing dsl.Thing, files []string, shouldErase string) (err error) { + for _, filename := range files { + err = dsl.ReadThingFromFile(thing, filename, ioutil.ReadFile) + if err != nil { + return + } + if shouldErase == "true" { + for _, t := range thing.InnerThings() { + err = dsl.DeleteThingToResource(resty.DefaultClient, t, ci, true) + if err != nil { + return + } + } + } else { + err = dsl.PostThingToResource(resty.DefaultClient, thing, ci, true) + if err != nil { + return + } + } + fmt.Fprintf(os.Stdout, "Updated %v Things from %v to %v \n", thing.Count(), filename, ci.Authority) + } + return +} + +// loadDevicesCmd represents the devices command +var loadDevicesCmd = &cobra.Command{ + Use: "devices", + Short: "Load Device configuration", + Long: `Load into a Healthbot installation the configuration for the Devices.`, + RunE: func(cmd *cobra.Command, args []string) (err error) { + files, err := getDirectoryContents(cmd.Flag("input_directory").Value.String()) + if err != nil { + return + } + err = loadThings(&dsl.Devices{}, files, cmd.Flag("erase").Value.String()) + return + }, +} + +func init() { + loadCmd.AddCommand(loadDevicesCmd) +} diff --git a/cmd/load_devices_test.go b/cmd/load_devices_test.go new file mode 100644 index 0000000..28c0590 --- /dev/null +++ b/cmd/load_devices_test.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "reflect" + "testing" + + "github.com/spf13/afero" +) + +func Test_getDirectoryContents(t *testing.T) { + + AppFs = afero.NewMemMapFs() + + matchFile := "something/anyfile.txt" + + _ = AppFs.Mkdir("nothing", 0777) + _ = AppFs.Mkdir("something", 0777) + _ = afero.WriteFile(AppFs, matchFile, []byte("junk"), 0777) + + type args struct { + inputDirectory string + } + tests := []struct { + name string + args args + wantMatches []string + wantErr bool + }{ + { + name: "no files, return nil", + args: args{ + inputDirectory: "nothing", + }, + wantMatches: nil, + wantErr: false, + }, + { + name: "sample file, return 1", + args: args{ + inputDirectory: "something", + }, + wantMatches: []string{matchFile}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotMatches, err := getDirectoryContents(tt.args.inputDirectory) + if (err != nil) != tt.wantErr { + t.Errorf("getDirectoryContents() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotMatches, tt.wantMatches) { + t.Errorf("getDirectoryContents() = %v, want %v", gotMatches, tt.wantMatches) + } + }) + } +} diff --git a/cmd/load_helper_files.go b/cmd/load_helper_files.go new file mode 100644 index 0000000..e578ad9 --- /dev/null +++ b/cmd/load_helper_files.go @@ -0,0 +1,85 @@ +package cmd + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/damianoneill/h7t/dsl" + "github.com/spf13/cobra" + "gopkg.in/resty.v1" +) + +// DeleteHelperFileToResource - Specific op for POSTING to Helper Files +func DeleteHelperFileToResource(rc *resty.Client, filename string, ci dsl.ConnectionInfo) (err error) { + resp, err := rc.R(). + SetBasicAuth(ci.Username, ci.Password). + Delete("https://" + ci.Authority + "/api/v1/files/helper-files/" + filepath.Base(filename) + "/") + if err != nil { + return + } + switch resp.StatusCode() { + case 204: + break + default: + return errors.New("Problem deleting File: %v " + resp.String()) + } + fmt.Fprintf(os.Stdout, "Deleted %v to %v \n", filename, ci.Authority) + return +} + +// PostHelperFileToResource - Specific op for POSTING to Helper Files +func PostHelperFileToResource(rc *resty.Client, filename string, ci dsl.ConnectionInfo) (err error) { + f, err := AppFs.Open(filename) + if err != nil { + return + } + defer f.Close() + resp, err := rc.R(). + SetBasicAuth(ci.Username, ci.Password). + SetFileReader("up_file", filepath.Base(filename), f). // stripping path from filename + Post("https://" + ci.Authority + "/api/v1/files/helper-files/" + filepath.Base(filename) + "/") + if err != nil { + return + } + switch resp.StatusCode() { + case 200: + break + default: + return errors.New("Problem uploading File: %v " + resp.String()) + } + fmt.Fprintf(os.Stdout, "Uploaded %v to %v \n", filename, ci.Authority) + return +} + +// helperFilesCmd represents the Helper Files command +var helperFilesCmd = &cobra.Command{ + Use: "helper-files", + Short: "Load Helper Files", + Long: `Load into a Healthbot installation the Helper Files e.g. any required python files.`, + RunE: func(cmd *cobra.Command, args []string) (err error) { + files, err := getDirectoryContents(cmd.Flag("input_directory").Value.String()) + if err != nil { + return + } + for _, filename := range files { + if cmd.Flag("erase").Value.String() == "true" { + err = DeleteHelperFileToResource(resty.DefaultClient, filename, ci) + if err != nil { + return + } + } else { + err = PostHelperFileToResource(resty.DefaultClient, filename, ci) + if err != nil { + return + } + } + } + return + }, +} + +func init() { + loadCmd.AddCommand(helperFilesCmd) +} diff --git a/cmd/load_helper_files_test.go b/cmd/load_helper_files_test.go new file mode 100644 index 0000000..332a211 --- /dev/null +++ b/cmd/load_helper_files_test.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "testing" + + "github.com/damianoneill/h7t/dsl" + "github.com/jarcoal/httpmock" + "github.com/spf13/afero" + "gopkg.in/resty.v1" +) + +func TestPostHelperFileToResource(t *testing.T) { + + AppFs = afero.NewMemMapFs() + matchFile := "something/anyfile.txt" + + _ = AppFs.Mkdir("something", 0777) + _ = afero.WriteFile(AppFs, matchFile, []byte("junk"), 0777) + + // Get the underlying HTTP Client and set it to Mock + httpmock.ActivateNonDefault(client.GetClient()) + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("POST", "https://localhost:8080/api/v1/files/helper-files/anyfile.txt/", + httpmock.NewStringResponder(200, ``)) + + type args struct { + rc *resty.Client + filename string + ci dsl.ConnectionInfo + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "valid file upload", + args: args{ + rc: client, + filename: matchFile, + ci: dsl.ConnectionInfo{ + Authority: "localhost:8080", + Username: "root", + Password: "changeme", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := PostHelperFileToResource(tt.args.rc, tt.args.filename, tt.args.ci); (err != nil) != tt.wantErr { + t.Errorf("PostHelperFileToResource() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..35bf7f4 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,114 @@ +package cmd + +import ( + "crypto/tls" + "fmt" + "os" + "path/filepath" + + "github.com/spf13/afero" + + "github.com/damianoneill/h7t/dsl" + "github.com/spf13/cobra" + "gopkg.in/resty.v1" + + "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" +) + +type buildInfo struct { + version string + commit string + date string +} + +var bi buildInfo + +var ci dsl.ConnectionInfo + +var cfgFile string + +var filePathSeperator = string(filepath.Separator) + +// AppFs - defined for testing +var AppFs = afero.NewOsFs() + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "h7t", + Short: "Healthbot Command Line Interface", + Long: `A tool for interacting with Healthbot over the REST API. + +The intent with this tool is to provide bulk or aggregate functions, that simplify interacting with Healthbot.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + if cmd.Flag("verbose").Value.String() == "true" { + resty.SetDebug(true) // will show rest calls + } + ci = dsl.ConnectionInfo{ + Authority: viper.GetString("authority"), + Username: viper.GetString("username"), + Password: viper.GetString("password"), + } + //setup resty + resty.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) + viper.Set("restclient.RedirectPolicy", "always") + }, + SilenceUsage: true, + SilenceErrors: true, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute(version, commit, date string) { + bi = buildInfo{version, commit, date} + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.h7t.yaml)") + + rootCmd.PersistentFlags().StringP("authority", "a", "localhost:8080", "healthbot HTTPS Authority") + rootCmd.PersistentFlags().StringP("username", "u", "admin", "healthbot Username") + rootCmd.PersistentFlags().StringP("password", "p", "****", "healthbot Password") + _ = viper.BindPFlag("authority", rootCmd.PersistentFlags().Lookup("authority")) + _ = viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username")) + _ = viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password")) + + rootCmd.PersistentFlags().BoolP("verbose", "v", false, "cause "+rootCmd.Use+" to be more verbose") + +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Search config in home directory with name ".h7t" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".h7t") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/cmd/summarise.go b/cmd/summarise.go new file mode 100644 index 0000000..b412c9e --- /dev/null +++ b/cmd/summarise.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// summariseCmd represents the summarise command +var summariseCmd = &cobra.Command{ + Use: "summarise", + Short: "Summarise information from a Healthbot Installation", + Long: `Summarise dsl from Healthbot for e.g. Devices, Device Groups, etc.`, + Run: func(cmd *cobra.Command, args []string) { + _ = cmd.Help() + }, +} + +func init() { + rootCmd.AddCommand(summariseCmd) +} diff --git a/cmd/summarise_installation.go b/cmd/summarise_installation.go new file mode 100644 index 0000000..be76970 --- /dev/null +++ b/cmd/summarise_installation.go @@ -0,0 +1,122 @@ +package cmd + +import ( + "fmt" + "io" + "os" + "strconv" + + "github.com/damianoneill/h7t/dsl" + "github.com/olekukonko/tablewriter" + "github.com/spf13/cobra" + "gopkg.in/resty.v1" +) + +// summariseInstallationCmd represents the installation command +var summariseInstallationCmd = &cobra.Command{ + Use: "installation", + Short: "Summarise information collected from a Healthbot installation", + Long: `Generates counts from different Healthbot dsl things for e.g. Devices, Device Groups etc.`, + RunE: func(cmd *cobra.Command, args []string) (err error) { + return summariseInstallation(ci) + }, +} + +// NewTable - provides a blank table for rendering. +func NewTable(out io.Writer) *tablewriter.Table { + table := tablewriter.NewWriter(out) + table.SetBorder(false) + table.SetColumnSeparator("") + table.SetHeaderLine(false) + table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + table.SetAutoFormatHeaders(false) + return table +} + +func collectSystemDetails(rc *resty.Client, ci dsl.ConnectionInfo, stdout io.Writer) (err error) { + sd := dsl.SystemDetails{} + err = dsl.ExtractThingFromResource(rc, &sd, ci) + if err != nil { + return + } + fmt.Fprintln(stdout, "") + fmt.Fprintf(stdout, "Healthbot Authority: %s \n", ci.Authority) + fmt.Fprintf(stdout, "Healthbot Version: %s \n", sd.Version) + fmt.Fprintf(stdout, "Healthbot Time: %s \n", sd.ServerTime) + return +} + +func collectDeviceFacts(rc *resty.Client, ci dsl.ConnectionInfo, stdout io.Writer) (df dsl.DeviceFacts, err error) { + err = dsl.ExtractThingFromResource(rc, &df, ci) + if err != nil { + return + } + fmt.Fprintln(stdout, "") + fmt.Fprintf(stdout, "No of Managed Devices: %v \n", len(df)) + fmt.Fprintln(stdout, "") + return +} + +func collectDeviceGroups(rc *resty.Client, ci dsl.ConnectionInfo, stdout io.Writer) (dg dsl.DeviceGroups, err error) { + err = dsl.ExtractThingFromResource(rc, &dg, ci) + if err != nil { + return + } + fmt.Fprintln(stdout, "") + fmt.Fprintf(stdout, "No of Device Groups: %v \n", len(dg.DeviceGroup)) + fmt.Fprintln(stdout, "") + return +} + +func renderDeviceTable(w io.Writer, df dsl.DeviceFacts) { + table := NewTable(w) + table.SetHeader([]string{"Device Id", "Platform", "Release", "Serial Number"}) + table.Append([]string{"", "", "", ""}) + for _, fact := range df { + table.Append([]string{fact.DeviceID, fact.Facts.Platform, fact.Facts.Release, fact.Facts.SerialNumber}) + } + table.Render() // Send output +} + +func renderDeviceGroups(w io.Writer, dg dsl.DeviceGroups) { + table := NewTable(w) + table.SetHeader([]string{"Device Group", "No of Devices"}) + for _, deviceGroup := range dg.DeviceGroup { + l := "0" + if deviceGroup.Devices != nil { + l = strconv.Itoa(len(*deviceGroup.Devices)) + } + table.Append([]string{deviceGroup.DeviceGroupName, l}) + } + table.Render() // Send output +} + +func summariseInstallation(ci dsl.ConnectionInfo) (err error) { + + err = collectSystemDetails(resty.DefaultClient, ci, os.Stdout) + if err != nil { + return + } + + df, err := collectDeviceFacts(resty.DefaultClient, ci, os.Stdout) + if err != nil { + return + } + + renderDeviceTable(os.Stdout, df) + + dg, err := collectDeviceGroups(resty.DefaultClient, ci, os.Stdout) + if err != nil { + return + } + + renderDeviceGroups(os.Stdout, dg) + + fmt.Println("") + + return +} + +func init() { + summariseCmd.AddCommand(summariseInstallationCmd) +} diff --git a/cmd/summarise_installation_test.go b/cmd/summarise_installation_test.go new file mode 100644 index 0000000..150fbbb --- /dev/null +++ b/cmd/summarise_installation_test.go @@ -0,0 +1,269 @@ +package cmd + +import ( + "bytes" + "io/ioutil" + "path/filepath" + "strings" + "testing" + + "github.com/damianoneill/h7t/dsl" + "github.com/jarcoal/httpmock" + "github.com/tj/assert" + "gopkg.in/resty.v1" +) + +var client = resty.New() + +func Test_collectSystemDetails(t *testing.T) { + type args struct { + ci dsl.ConnectionInfo + } + tests := []struct { + name string + args args + wantStdoutContains string + wantErr bool + }{ + { + name: "Test invalid system details", + args: args{ + ci: dsl.ConnectionInfo{ + Authority: "localhost:8080", + Username: "root", + Password: "changeme", + }, + }, + wantStdoutContains: "", + wantErr: true, + }, + { + name: "Test valid system details", + args: args{ + ci: dsl.ConnectionInfo{ + Authority: "localhost:8080", + Username: "root", + Password: "changeme", + }, + }, + wantStdoutContains: "2019-11-12T11:23:53Z", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.wantErr { + // create valid resty mock + httpmock.ActivateNonDefault(client.GetClient()) + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("GET", "https://localhost:8080/api/v1/system-details/", + httpmock.NewStringResponder(200, `{"server-time": "2019-11-12T11:23:53Z","version": "HealthBot 2.1.0-beta"}`)) + + } + stdout := &bytes.Buffer{} + if err := collectSystemDetails(client, tt.args.ci, stdout); (err != nil) != tt.wantErr { + t.Errorf("collectSystemDetails() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr { + if !strings.Contains(stdout.String(), tt.wantStdoutContains) { + t.Errorf("collectSystemDetails() = %v, should contain %v", stdout.String(), tt.wantStdoutContains) + } + } + }) + } +} + +func Test_collectDeviceFacts(t *testing.T) { + type args struct { + ci dsl.ConnectionInfo + } + tests := []struct { + name string + args args + wantStdoutContains string + wantErr bool + }{ + { + name: "Test invalid device facts", + args: args{ + ci: dsl.ConnectionInfo{ + Authority: "localhost:8080", + Username: "root", + Password: "changeme", + }, + }, + wantStdoutContains: "", + wantErr: true, + }, + { + name: "Test valid device facts", + args: args{ + ci: dsl.ConnectionInfo{ + Authority: "localhost:8080", + Username: "root", + Password: "changeme", + }, + }, + wantStdoutContains: "Managed Devices: 1", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.wantErr { + // create valid resty mock + httpmock.ActivateNonDefault(client.GetClient()) + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("GET", "https://localhost:8080/api/v1/devices/facts/", + httpmock.NewStringResponder(200, `[{"device-id":"mx960-3","facts":{"fpc":[{"description":"MPC7E 3D MRATE-12xQSFPP-XGE-XLGE-CGE","model-number":"MPC7E-MRATE","name":"FPC 11","part-number":"750-056519","serial-number":"CAFR4421","version":"REV 36"}],"hostname":"mx960-3","junos-info":[{"last-reboot-reason":"0x4000:VJUNOS reboot","mastership-state":"master","model":"RE-S-2X00x6","name":"re0","status":"OK","up-time":"30 days, 5 hours, 59 minutes, 51 seconds","version-info":{"build":8,"major":[19,3],"minor":["1"],"type":"R"}}],"platform":"MX960","platform-info":[{"name":"re0","platform":"MX960"}],"product":"MX","release":"19.3R1.8","serial-number":"JN1233EF1AFA"}}]`)) + + } + stdout := &bytes.Buffer{} + var df dsl.DeviceFacts + var err error + if df, err = collectDeviceFacts(client, tt.args.ci, stdout); (err != nil) != tt.wantErr { + t.Errorf("collectDeviceFacts() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr { + if !strings.Contains(stdout.String(), tt.wantStdoutContains) { + t.Errorf("collectDeviceFacts() = %v, should contain %v", stdout.String(), tt.wantStdoutContains) + } + assert.Len(t, df, 1, "should contain 1 device") + } + }) + } +} + +// HelperLoadBytes allows you to use relative path testdata directory as a place +// to load and store your data +func HelperLoadBytes(tb testing.TB, name string) []byte { + path := filepath.Join("testdata", name) // relative path + bytes, err := ioutil.ReadFile(path) // nolint : gosec + if err != nil { + tb.Fatal(err) + } + return bytes +} + +func Test_renderDeviceTable(t *testing.T) { + + b := HelperLoadBytes(t, "device-facts.json") + df := dsl.DeviceFacts{} + _ = df.Unmarshal(b) + + type args struct { + df dsl.DeviceFacts + } + tests := []struct { + name string + args args + wantW string + }{ + { + name: "Should contain MX960", + args: args{df: df}, + wantW: "MX960 19.3R1.8", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &bytes.Buffer{} + renderDeviceTable(w, tt.args.df) + if gotW := w.String(); !strings.Contains(gotW, tt.wantW) { + t.Errorf("renderDeviceTable() = %v, should contain %v", gotW, tt.wantW) + } + }) + } +} + +func Test_collectDeviceGroups(t *testing.T) { + type args struct { + rc *resty.Client + ci dsl.ConnectionInfo + } + tests := []struct { + name string + args args + wantDg dsl.DeviceGroups + wantStdout string + wantErr bool + }{ + { + name: "valid request", + args: args{ + rc: client, + ci: dsl.ConnectionInfo{ + Authority: "localhost:8080", + Username: "root", + Password: "changeme", + }, + }, + wantDg: dsl.DeviceGroups{ + DeviceGroup: []dsl.DeviceGroup{{ + DeviceGroupName: "dg1", + }}, + }, + wantStdout: "No of Device Groups: 1", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + // create valid resty mock + httpmock.ActivateNonDefault(client.GetClient()) + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("GET", "https://localhost:8080/api/v1/device-groups/", + httpmock.NewStringResponder(200, `{"device-group":[{"device-group-name":"dg1","devices":["mx960-1"]}]}`)) + + stdout := &bytes.Buffer{} + _, err := collectDeviceGroups(tt.args.rc, tt.args.ci, stdout) + if (err != nil) != tt.wantErr { + t.Errorf("collectDeviceGroups() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotStdout := stdout.String(); !strings.Contains(gotStdout, tt.wantStdout) { + t.Errorf("collectDeviceGroups() = %v, should contain %v", gotStdout, tt.wantStdout) + } + }) + } +} + +func Test_renderDeviceGroups(t *testing.T) { + type args struct { + dg dsl.DeviceGroups + } + tests := []struct { + name string + args args + wantW string + }{ + { + name: "should contain device group with no devices", + args: args{ + dg: dsl.DeviceGroups{ + DeviceGroup: []dsl.DeviceGroup{{ + DeviceGroupName: "dg1", + }}, + }, + }, + wantW: "dg1 0", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &bytes.Buffer{} + renderDeviceGroups(w, tt.args.dg) + if gotW := w.String(); !strings.Contains(gotW, tt.wantW) { + t.Errorf("renderDeviceGroups() = %v, should contain %v", gotW, tt.wantW) + } + }) + } +} diff --git a/cmd/testdata/device-facts.json b/cmd/testdata/device-facts.json new file mode 100644 index 0000000..fde04ad --- /dev/null +++ b/cmd/testdata/device-facts.json @@ -0,0 +1,123 @@ +[ + { + "device-id": "mx960-3", + "facts": { + "fpc": [ + { + "description": "MPC7E 3D MRATE-12xQSFPP-XGE-XLGE-CGE", + "model-number": "MPC7E-MRATE", + "name": "FPC 11", + "part-number": "750-056519", + "serial-number": "CAFR4421", + "version": "REV 36" + } + ], + "hostname": "mx960-3", + "junos-info": [ + { + "last-reboot-reason": "0x4000:VJUNOS reboot", + "mastership-state": "master", + "model": "RE-S-2X00x6", + "name": "re0", + "status": "OK", + "up-time": "30 days, 5 hours, 59 minutes, 51 seconds", + "version-info": { + "build": 8, + "major": [ + 19, + 3 + ], + "minor": [ + "1" + ], + "type": "R" + } + } + ], + "platform": "MX960", + "platform-info": [ + { + "name": "re0", + "platform": "MX960" + } + ], + "product": "MX", + "release": "19.3R1.8", + "serial-number": "JN1233EF1AFA" + } + }, + { + "device-id": "mx960-1", + "facts": { + "fpc": [ + { + "description": "MPC7E 3D MRATE-12xQSFPP-XGE-XLGE-CGE", + "model-number": "MPC7E-MRATE", + "name": "FPC 11", + "part-number": "750-056519", + "serial-number": "CAGD3519", + "version": "REV 40" + } + ], + "hostname": "mx960-1", + "junos-info": [ + { + "last-reboot-reason": "0x4000:VJUNOS reboot", + "mastership-state": "master", + "model": "RE-S-2X00x6", + "name": "re0", + "status": "OK", + "up-time": "30 days, 4 hours, 52 minutes, 51 seconds", + "version-info": { + "build": 8, + "major": [ + 19, + 3 + ], + "minor": [ + "1" + ], + "type": "R" + } + }, + { + "last-reboot-reason": "0x4000:VJUNOS reboot", + "mastership-state": "backup", + "model": "RE-S-2X00x6", + "name": "re1", + "status": "OK", + "up-time": "30 days, 5 hours, 7 seconds", + "version-info": { + "build": 8, + "major": [ + 19, + 3 + ], + "minor": [ + "1" + ], + "type": "R" + } + } + ], + "platform": "MX960", + "platform-info": [ + { + "name": "re0", + "platform": "MX960" + }, + { + "name": "re1", + "platform": "MX960" + } + ], + "product": "MX", + "release": "19.3R1.8", + "serial-number": "JN1232C39AFA" + } + }, + { + "device-id": "rubbish", + "facts": {} + } +] \ No newline at end of file diff --git a/cmd/transform.go b/cmd/transform.go new file mode 100644 index 0000000..43fba5b --- /dev/null +++ b/cmd/transform.go @@ -0,0 +1,32 @@ +package cmd + +import ( + "github.com/hashicorp/go-hclog" + "github.com/spf13/cobra" +) + +var logLevel = hclog.Info + +// transformCmd represents the transform command +var transformCmd = &cobra.Command{ + Use: "transform", + Short: "Transform things from proprietary formats into Healthbot dsl format", + Long: `Transform customer content into dsl things for e.g. devices. + +Transform sub-commands work by iterating over all files in the input directory`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + if cmd.Flag("verbose").Value.String() == "true" { + logLevel = hclog.Debug + } + }, + Run: func(cmd *cobra.Command, args []string) { + _ = cmd.Help() + }, +} + +func init() { + rootCmd.AddCommand(transformCmd) + transformCmd.PersistentFlags().StringP("output_directory", "o", ".", "directory where the configuration will be written to") + transformCmd.PersistentFlags().StringP("input_directory", "i", ".", "directory where the configuration will be loaded from") + transformCmd.PersistentFlags().String("plugin", "csv", "name of the plugin to be used") +} diff --git a/cmd/transform_devices.go b/cmd/transform_devices.go new file mode 100644 index 0000000..dbfe018 --- /dev/null +++ b/cmd/transform_devices.go @@ -0,0 +1,88 @@ +package cmd + +import ( + "fmt" + "log" + "os" + "os/exec" + + "github.com/damianoneill/h7t/plugins" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" + "github.com/spf13/cobra" +) + +// handshakeConfigs are used to just do a basic handshake between +// a plugin and host. +var handshakeConfig = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "BASIC_PLUGIN", + MagicCookieValue: "csv", +} + +// pluginMap is the map of plugins we can dispense. +var pluginMap = map[string]plugin.Plugin{ + "transformer": &plugins.TransformerPlugin{}, +} + +// devicesCmd represents the devices command +var tranformDevicesCmd = &cobra.Command{ + Use: "devices", + Short: "Transform Devices configuration", + Long: `Transform Devices configurations from comma separated value (csv) format into the dsl format using a bundled plugin.`, + RunE: func(cmd *cobra.Command, args []string) (err error) { + inputDirectory := cmd.Flag("input_directory").Value.String() + outputDirectory := cmd.Flag("output_directory").Value.String() + plugin := cmd.Flag("plugin").Value.String() + return transformDevices(inputDirectory, outputDirectory, plugin, args) + }, +} + +func transformDevices(inputDirectory, outputDirectory, p string, args []string) (err error) { + fmt.Fprintf(os.Stdout, "Plugin: %v \n", p) + + /// Generic Plugin Code + + // Create an hclog.Logger + logger := hclog.New(&hclog.LoggerOptions{ + Name: "plugin", + Output: os.Stdout, + Level: logLevel, + }) + // We're a host! Start by launching the plugin process. + client := plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: handshakeConfig, + Plugins: pluginMap, + Cmd: exec.Command("./plugins/csv/transformer"), + Logger: logger, + }) + defer client.Kill() + // Connect via RPC + rpcClient, err := client.Client() + if err != nil { + log.Fatal(err) + } + // Request the plugin + raw, err := rpcClient.Dispense("transformer") + if err != nil { + log.Fatal(err) + } + transformer := raw.(plugins.Transformer) + devices, err := transformer.Devices(plugins.Arguments{ + InputDirectory: inputDirectory, + CmdLineArgs: args, + }) + + /// End of Generic Plugin code + + if err != nil { + fmt.Fprintf(os.Stdout, "Error: %v \n", err) + return + } + + return WriteThingsToFile(&devices, outputDirectory+filePathSeperator+"devices.yml") +} + +func init() { + transformCmd.AddCommand(tranformDevicesCmd) +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..3fabcc6 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,27 @@ +package cmd + +import ( + "fmt" + "io" + "os" + + "github.com/spf13/cobra" +) + +// versionCmd represents the version command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Output the current build information", + Long: "Version, Commit and Date will be output from the Build Info.", + Run: func(cmd *cobra.Command, args []string) { + version(os.Stdout, bi) + }, +} + +func version(w io.Writer, bi buildInfo) { + fmt.Fprintf(w, "%v, commit %v, built at %v \n", bi.version, bi.commit, bi.date) +} + +func init() { + rootCmd.AddCommand(versionCmd) +} diff --git a/cmd/version_test.go b/cmd/version_test.go new file mode 100644 index 0000000..ef94608 --- /dev/null +++ b/cmd/version_test.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "bytes" + "testing" +) + +func Test_version(t *testing.T) { + type args struct { + bi buildInfo + } + tests := []struct { + name string + args args + wantW string + }{ + {"valid version output", + args{ + buildInfo{ + "0.3.0", + "c26cfaca0e38465935c48b13ae99d12fbf5d7cb1", + "2019-11-05T21:16:06Z", + }, + }, + "0.3.0, commit c26cfaca0e38465935c48b13ae99d12fbf5d7cb1, built at 2019-11-05T21:16:06Z \n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &bytes.Buffer{} + version(w, tt.args.bi) + if gotW := w.String(); gotW != tt.wantW { + t.Errorf("version() = --%v--, want --%v--", gotW, tt.wantW) + } + }) + } +} diff --git a/docs/h7t.md b/docs/h7t.md new file mode 100644 index 0000000..664cda2 --- /dev/null +++ b/docs/h7t.md @@ -0,0 +1,33 @@ +## h7t + +Healthbot Command Line Interface + +### Synopsis + +A tool for interacting with Healthbot over the REST API. + +The intent with this tool is to provide bulk or aggregate functions, that simplify interacting with Healthbot. + +### Options + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -h, --help help for h7t + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t completion](h7t_completion.md) - Generate shell completion script for h7t +* [h7t configure](h7t_configure.md) - Configure information relating to a Healthbot Installation +* [h7t docs](h7t_docs.md) - Generate Markdown for the commands in h7t +* [h7t extract](h7t_extract.md) - Extract information from a Healthbot Installation +* [h7t load](h7t_load.md) - Load information into a Healthbot Installation +* [h7t summarise](h7t_summarise.md) - Summarise information from a Healthbot Installation +* [h7t transform](h7t_transform.md) - Transform things from proprietary formats into Healthbot dsl format +* [h7t version](h7t_version.md) - Output the current build information + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_completion.md b/docs/h7t_completion.md new file mode 100644 index 0000000..46f4522 --- /dev/null +++ b/docs/h7t_completion.md @@ -0,0 +1,57 @@ +## h7t completion + +Generate shell completion script for h7t + +### Synopsis + +Generates a shell completion script for h7t. + NOTE: The current version supports Bash only. + This should work for *nix systems with Bash installed. + + By default, the file is written directly to /etc/bash_completion.d + for convenience, and the command may need superuser rights, e.g.: + + $ sudo h7t completion + + Add `--completionfile=/path/to/file` flag to set alternative + file-path and name. + + For e.g. on OSX with bash completion installed with brew you should + + $ h7t completion --completionfile $(brew --prefix)/etc/bash_completion.d/h7t.sh + + Logout and in again to reload the completion scripts, + or just source them directly: + + $ . /etc/bash_completion + + or using if using brew + + $ . $(brew --prefix)/etc/bash_completion + +``` +h7t completion [flags] +``` + +### Options + +``` + --completionfile string completion file (default "/etc/bash_completion.d/h7t.sh") + -h, --help help for completion +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t](h7t.md) - Healthbot Command Line Interface + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_configure.md b/docs/h7t_configure.md new file mode 100644 index 0000000..f228599 --- /dev/null +++ b/docs/h7t_configure.md @@ -0,0 +1,34 @@ +## h7t configure + +Configure information relating to a Healthbot Installation + +### Synopsis + +Configure components involved in a Healthbot installation e.g. Devices. + +``` +h7t configure [flags] +``` + +### Options + +``` + -h, --help help for configure +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t](h7t.md) - Healthbot Command Line Interface +* [h7t configure devices](h7t_configure_devices.md) - Load Devices with configuration + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_configure_devices.md b/docs/h7t_configure_devices.md new file mode 100644 index 0000000..0268e9c --- /dev/null +++ b/docs/h7t_configure_devices.md @@ -0,0 +1,59 @@ +## h7t configure devices + +Load Devices with configuration + +### Synopsis + +Load into Devices, configuration defined in the netconf rpc file. + +E.g. + +$ cat sample.rpc + + + + + + + + + + + + + + + + + + + + + +``` +h7t configure devices [flags] +``` + +### Options + +``` + -h, --help help for devices + -i, --input_directory string directory where the device configuration will be loaded from (default ".") + -f, --netconf_rpc string file that contains a netconf rpc +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t configure](h7t_configure.md) - Configure information relating to a Healthbot Installation + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_docs.md b/docs/h7t_docs.md new file mode 100644 index 0000000..2b7fead --- /dev/null +++ b/docs/h7t_docs.md @@ -0,0 +1,33 @@ +## h7t docs + +Generate Markdown for the commands in h7t + +### Synopsis + +For h7t generate Markdown Documents for each of the commands and write them to a folder named ./docs + +``` +h7t docs [flags] +``` + +### Options + +``` + -h, --help help for docs +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t](h7t.md) - Healthbot Command Line Interface + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_extract.md b/docs/h7t_extract.md new file mode 100644 index 0000000..d9d339d --- /dev/null +++ b/docs/h7t_extract.md @@ -0,0 +1,36 @@ +## h7t extract + +Extract information from a Healthbot Installation + +### Synopsis + +Extract dsl from Healthbot for e.g. Devices, Device Groups, etc. + +``` +h7t extract [flags] +``` + +### Options + +``` + -h, --help help for extract + -o, --output_directory string directory where the configuration will be stored (default ".") +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t](h7t.md) - Healthbot Command Line Interface +* [h7t extract device-groups](h7t_extract_device-groups.md) - Extract Device Groups configuration +* [h7t extract devices](h7t_extract_devices.md) - Extract Device configuration + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_extract_device-groups.md b/docs/h7t_extract_device-groups.md new file mode 100644 index 0000000..96d13d9 --- /dev/null +++ b/docs/h7t_extract_device-groups.md @@ -0,0 +1,34 @@ +## h7t extract device-groups + +Extract Device Groups configuration + +### Synopsis + +Collect from a Healthbot installation the configuration for the Devices Groups. + +``` +h7t extract device-groups [flags] +``` + +### Options + +``` + -h, --help help for device-groups +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -o, --output_directory string directory where the configuration will be stored (default ".") + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t extract](h7t_extract.md) - Extract information from a Healthbot Installation + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_extract_devices.md b/docs/h7t_extract_devices.md new file mode 100644 index 0000000..5e00799 --- /dev/null +++ b/docs/h7t_extract_devices.md @@ -0,0 +1,34 @@ +## h7t extract devices + +Extract Device configuration + +### Synopsis + +Collect from a Healthbot installation the configuration for the Devices. + +``` +h7t extract devices [flags] +``` + +### Options + +``` + -h, --help help for devices +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -o, --output_directory string directory where the configuration will be stored (default ".") + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t extract](h7t_extract.md) - Extract information from a Healthbot Installation + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_load.md b/docs/h7t_load.md new file mode 100644 index 0000000..55c85ff --- /dev/null +++ b/docs/h7t_load.md @@ -0,0 +1,40 @@ +## h7t load + +Load information into a Healthbot Installation + +### Synopsis + +Load dsl into Healthbot for e.g. Devices, Device Groups, etc.. + +Load sub-commands work by iterating over all files in the input directory + +``` +h7t load [flags] +``` + +### Options + +``` + -e, --erase erase the thing(s) identified in configuration + -h, --help help for load + -i, --input_directory string directory where the configuration will be loaded from (default ".") +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t](h7t.md) - Healthbot Command Line Interface +* [h7t load device-groups](h7t_load_device-groups.md) - Load Device Groups configuration +* [h7t load devices](h7t_load_devices.md) - Load Device configuration +* [h7t load helper-files](h7t_load_helper-files.md) - Load Helper Files + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_load_device-groups.md b/docs/h7t_load_device-groups.md new file mode 100644 index 0000000..b189f66 --- /dev/null +++ b/docs/h7t_load_device-groups.md @@ -0,0 +1,35 @@ +## h7t load device-groups + +Load Device Groups configuration + +### Synopsis + +Load into a Healthbot installation the configuration for the Device Groups. + +``` +h7t load device-groups [flags] +``` + +### Options + +``` + -h, --help help for device-groups +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -e, --erase erase the thing(s) identified in configuration + -i, --input_directory string directory where the configuration will be loaded from (default ".") + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t load](h7t_load.md) - Load information into a Healthbot Installation + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_load_devices.md b/docs/h7t_load_devices.md new file mode 100644 index 0000000..11d7f82 --- /dev/null +++ b/docs/h7t_load_devices.md @@ -0,0 +1,35 @@ +## h7t load devices + +Load Device configuration + +### Synopsis + +Load into a Healthbot installation the configuration for the Devices. + +``` +h7t load devices [flags] +``` + +### Options + +``` + -h, --help help for devices +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -e, --erase erase the thing(s) identified in configuration + -i, --input_directory string directory where the configuration will be loaded from (default ".") + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t load](h7t_load.md) - Load information into a Healthbot Installation + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_load_helper-files.md b/docs/h7t_load_helper-files.md new file mode 100644 index 0000000..9d7405b --- /dev/null +++ b/docs/h7t_load_helper-files.md @@ -0,0 +1,35 @@ +## h7t load helper-files + +Load Helper Files + +### Synopsis + +Load into a Healthbot installation the Helper Files e.g. any required python files. + +``` +h7t load helper-files [flags] +``` + +### Options + +``` + -h, --help help for helper-files +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -e, --erase erase the thing(s) identified in configuration + -i, --input_directory string directory where the configuration will be loaded from (default ".") + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t load](h7t_load.md) - Load information into a Healthbot Installation + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_summarise.md b/docs/h7t_summarise.md new file mode 100644 index 0000000..cbbf739 --- /dev/null +++ b/docs/h7t_summarise.md @@ -0,0 +1,34 @@ +## h7t summarise + +Summarise information from a Healthbot Installation + +### Synopsis + +Summarise dsl from Healthbot for e.g. Devices, Device Groups, etc. + +``` +h7t summarise [flags] +``` + +### Options + +``` + -h, --help help for summarise +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t](h7t.md) - Healthbot Command Line Interface +* [h7t summarise installation](h7t_summarise_installation.md) - Summarise information collected from a Healthbot installation + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_summarise_installation.md b/docs/h7t_summarise_installation.md new file mode 100644 index 0000000..cec6029 --- /dev/null +++ b/docs/h7t_summarise_installation.md @@ -0,0 +1,33 @@ +## h7t summarise installation + +Summarise information collected from a Healthbot installation + +### Synopsis + +Generates counts from different Healthbot dsl things for e.g. Devices, Device Groups etc. + +``` +h7t summarise installation [flags] +``` + +### Options + +``` + -h, --help help for installation +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t summarise](h7t_summarise.md) - Summarise information from a Healthbot Installation + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_transform.md b/docs/h7t_transform.md new file mode 100644 index 0000000..60a0a38 --- /dev/null +++ b/docs/h7t_transform.md @@ -0,0 +1,39 @@ +## h7t transform + +Transform things from proprietary formats into Healthbot dsl format + +### Synopsis + +Transform customer content into dsl things for e.g. devices. + +Transform sub-commands work by iterating over all files in the input directory + +``` +h7t transform [flags] +``` + +### Options + +``` + -h, --help help for transform + -i, --input_directory string directory where the configuration will be loaded from (default ".") + -o, --output_directory string directory where the configuration will be written to (default ".") + --plugin string name of the plugin to be used (default "csv") +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t](h7t.md) - Healthbot Command Line Interface +* [h7t transform devices](h7t_transform_devices.md) - Transform Devices configuration + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_transform_devices.md b/docs/h7t_transform_devices.md new file mode 100644 index 0000000..eae2d58 --- /dev/null +++ b/docs/h7t_transform_devices.md @@ -0,0 +1,36 @@ +## h7t transform devices + +Transform Devices configuration + +### Synopsis + +Transform Devices configurations from comma separated value (csv) format into the dsl format using a bundled plugin. + +``` +h7t transform devices [flags] +``` + +### Options + +``` + -h, --help help for devices +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -i, --input_directory string directory where the configuration will be loaded from (default ".") + -o, --output_directory string directory where the configuration will be written to (default ".") + -p, --password string healthbot Password (default "****") + --plugin string name of the plugin to be used (default "csv") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t transform](h7t_transform.md) - Transform things from proprietary formats into Healthbot dsl format + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/docs/h7t_version.md b/docs/h7t_version.md new file mode 100644 index 0000000..51e3463 --- /dev/null +++ b/docs/h7t_version.md @@ -0,0 +1,33 @@ +## h7t version + +Output the current build information + +### Synopsis + +Version, Commit and Date will be output from the Build Info. + +``` +h7t version [flags] +``` + +### Options + +``` + -h, --help help for version +``` + +### Options inherited from parent commands + +``` + -a, --authority string healthbot HTTPS Authority (default "localhost:8080") + --config string config file (default is $HOME/.h7t.yaml) + -p, --password string healthbot Password (default "****") + -u, --username string healthbot Username (default "admin") + -v, --verbose cause h7t to be more verbose +``` + +### SEE ALSO + +* [h7t](h7t.md) - Healthbot Command Line Interface + +###### Auto generated by spf13/cobra on 14-Nov-2019 diff --git a/dsl/deviceFacts.go b/dsl/deviceFacts.go new file mode 100644 index 0000000..9e788c1 --- /dev/null +++ b/dsl/deviceFacts.go @@ -0,0 +1,45 @@ +package dsl + +// DeviceFacts - Provides Device Facts +type DeviceFacts []struct { + DeviceID string `json:"device-id" yaml:"device-id"` + Facts struct { + Hostname string `json:"hostname"` + JunosInfo []struct { + LastRebootReason string `json:"last-reboot-reason"` + MastershipState string `json:"mastership-state"` + Model string `json:"model"` + Name string `json:"name"` + Status string `json:"status"` + UpTime string `json:"up-time"` + } `json:"junos-info"` + Platform string `json:"platform"` + PlatformInfo []struct { + Name string `json:"name"` + Platform string `json:"platform"` + } `json:"platform-info"` + Product string `json:"product"` + Release string `json:"release"` + SerialNumber string `json:"serial-number" yaml:"serial-number"` + } `json:"facts,omitempty"` +} + +// Unmarshal - tries to Unmarshal yaml first, then json into the DeviceFacts struct +func (d *DeviceFacts) Unmarshal(data []byte) error { + return unmarshal(data, d) +} + +// Path - resource path for DeviceFacts +func (d *DeviceFacts) Path() string { + return "/api/v1/devices/facts/" +} + +// Count - no of components within a thing +func (d *DeviceFacts) Count() int { + return 1 +} + +// InnerThings - returns inner things or empty slice +func (d *DeviceFacts) InnerThings() []Thing { + return []Thing{} +} diff --git a/dsl/deviceGroups.go b/dsl/deviceGroups.go new file mode 100644 index 0000000..709dba1 --- /dev/null +++ b/dsl/deviceGroups.go @@ -0,0 +1,73 @@ +package dsl + +// DeviceGroups - collection of Device Groups +type DeviceGroups struct { + DeviceGroup []DeviceGroup `json:"device-group" yaml:"device-group"` +} + +// DGAuthentication - Option to Override the individual Device Username/Passwords +type DGAuthentication struct { + Password struct { + Password *string `json:"password"` + Username *string `json:"username"` + } `json:"password,omitempty" yaml:"password,omitempty"` +} + +// NativeGpb - Override the default JTI Port(s) +type NativeGpb struct { + Ports []int `json:"ports"` +} + +// DeviceGroup - info needed to Register a DeviceGroup in Healthbot +type DeviceGroup struct { + DeviceGroupName string `json:"device-group-name" yaml:"device-group-name"` + Description *string `json:"description,omitempty" yaml:"description,omitempty"` + Devices *[]string `json:"devices,omitempty" yaml:"devices,omitempty"` + Playbooks *[]string `json:"playbooks,omitempty" yaml:"playbooks,omitempty"` + Authentication *DGAuthentication `json:"authentication,omitempty" yaml:"authentication,omitempty"` + NativeGpb *NativeGpb `json:"native-gpb,omitempty" yaml:"native-gpb,omitempty"` +} + +// Unmarshal - tries to Unmarshal yaml first, then json into the DeviceGroups struct +func (d *DeviceGroups) Unmarshal(data []byte) error { + return unmarshal(data, d) +} + +// Path - resource path for DeviceGroups +func (d *DeviceGroups) Path() string { + return "/api/v1/device-groups/" +} + +// Count - no of components within a thing +func (d *DeviceGroups) Count() int { + return len(d.DeviceGroup) +} + +// InnerThings - returns inner things or empty slice +func (d *DeviceGroups) InnerThings() []Thing { + b := make([]Thing, len(d.DeviceGroup)) + for i := range d.DeviceGroup { + b[i] = &d.DeviceGroup[i] + } + return b +} + +// Unmarshal - tries to Unmarshal yaml first, then json into the DeviceGroup struct +func (d *DeviceGroup) Unmarshal(data []byte) error { + return unmarshal(data, d) +} + +// Path - resource path for DeviceGroup +func (d *DeviceGroup) Path() string { + return "/api/v1/device-group/" + d.DeviceGroupName + "/" +} + +// Count - no of components within a thing +func (d *DeviceGroup) Count() int { + return 1 +} + +// InnerThings - returns inner things or empty slice +func (d *DeviceGroup) InnerThings() []Thing { + return []Thing{} +} diff --git a/dsl/devices.go b/dsl/devices.go new file mode 100644 index 0000000..798aaea --- /dev/null +++ b/dsl/devices.go @@ -0,0 +1,110 @@ +package dsl + +// Devices - collection of Device +type Devices struct { + Device []Device `json:"device"` +} + +// Password - wrapper for uname/password +type Password struct { + Username *string `json:"username" csv:"username,omitempty"` + Password *string `json:"password" csv:"password,omitempty"` +} + +// Authentication - Collection type for Auth options +type Authentication struct { + Password `json:"password,omitempty" yaml:"password,omitempty"` +} + +// IAgent - configure the NETCONF port +type IAgent struct { + Port int `json:"port"` +} + +// OpenConfig - configure the Open Config port +type OpenConfig struct { + Port int `json:"port"` +} + +// V2 - configure the SNMP community string +type V2 struct { + Community string `json:"community"` +} + +// Snmp - configure the SNMP port or Community String +type Snmp struct { + V2 *V2 `json:"v2,omitempty" yaml:"v2,omitempty"` + Port int `json:"port,omitempty" yaml:"port,omitempty"` +} + +// Juniper - option to define the Operating system +type Juniper struct { + OperatingSystem string `json:"operating-system" yaml:"operating-system"` +} + +// Cisco - option to define the Operating system +type Cisco struct { + OperatingSystem string `json:"operating-system" yaml:"operating-system"` +} + +// Vendor - Configure the Vendor information +type Vendor struct { + Juniper *Juniper `json:"juniper,omitempty" yaml:"juniper,omitempty"` + Cisco *Cisco `json:"cisco,omitempty" yaml:"cisco,omitempty"` +} + +// Device - info needed to Register a Device in Healthbot +type Device struct { + DeviceID string `json:"device-id" yaml:"device-id" csv:"device-id"` + Host string `json:"host" csv:"host"` + SystemID *string `json:"system-id,omitempty" yaml:"system-id,omitempty" csv:"-"` + *Authentication `json:"authentication,omitempty" yaml:"authentication,omitempty"` + IAgent *IAgent `json:"iAgent,omitempty" yaml:"iAgent,omitempty" csv:"-"` + OpenConfig *OpenConfig `json:"open-config,omitempty" yaml:"open-config,omitempty" csv:"-"` + Snmp *Snmp `json:"snmp,omitempty" yaml:"snmp,omitempty" csv:"-"` + Vendor *Vendor `json:"vendor,omitempty" yaml:"vendor,omitempty" csv:"-"` +} + +// Unmarshal - tries to Unmarshal yaml first, then json into the Devices struct +func (d *Devices) Unmarshal(data []byte) error { + return unmarshal(data, d) +} + +// Path - resource path for Devices +func (d *Devices) Path() string { + return "/api/v1/devices/" +} + +// Count - no of components within a thing +func (d *Devices) Count() int { + return len(d.Device) +} + +// InnerThings - returns inner things or empty slice +func (d *Devices) InnerThings() []Thing { + b := make([]Thing, len(d.Device)) + for i := range d.Device { + b[i] = &d.Device[i] + } + return b +} + +// Unmarshal - tries to Unmarshal yaml first, then json into the Device struct +func (d *Device) Unmarshal(data []byte) error { + return unmarshal(data, d) +} + +// Path - resource path for Device +func (d *Device) Path() string { + return "/api/v1/device/" + d.DeviceID + "/" +} + +// Count - no of components within a thing +func (d *Device) Count() int { + return 1 +} + +// InnerThings - returns inner things or empty slice +func (d *Device) InnerThings() []Thing { + return []Thing{} +} diff --git a/dsl/systemDetails.go b/dsl/systemDetails.go new file mode 100644 index 0000000..99b9856 --- /dev/null +++ b/dsl/systemDetails.go @@ -0,0 +1,27 @@ +package dsl + +// SystemDetails - Provides some basic hb info +type SystemDetails struct { + ServerTime string `json:"server-time" yaml:"server-time"` + Version string `json:"version"` +} + +// Unmarshal - tries to Unmarshal yaml first, then json into the Devices struct +func (sd *SystemDetails) Unmarshal(data []byte) error { + return unmarshal(data, sd) +} + +// Path - resource path for Devices +func (sd *SystemDetails) Path() string { + return "/api/v1/system-details/" +} + +// Count - no of components within a thing +func (sd *SystemDetails) Count() int { + return 1 +} + +// InnerThings - returns inner things or empty slice +func (sd *SystemDetails) InnerThings() []Thing { + return []Thing{} +} diff --git a/dsl/testdata/devices.yml b/dsl/testdata/devices.yml new file mode 100644 index 0000000..4c1ee65 --- /dev/null +++ b/dsl/testdata/devices.yml @@ -0,0 +1,33 @@ +--- +device: + - device-id: "4200_1" # all params + host: 10.99.67.106 + system-id: "4200_1" + authentication: + password: + password: "$9$UTHP59A0Ecl0ORSyeXxUjiqfz/Ct" + username: labautotest1 + iAgent: + port: 830 + open-config: + port: 32767 + snmp: + port: 161 + v2: + community: public + vendor: + juniper: + operating-system: junos + - device-id: mx960-1 # min with auth, cisco and part of snmp + host: 172.30.177.102 + snmp: + port: 161 + authentication: + password: + password: "$9$.mQ3EhrvMX0BIcrlLXGDjkfT369" + username: doneill + vendor: + cisco: + operating-system: iosxr + - device-id: mx960-3 # min + host: 172.30.177.113 diff --git a/dsl/things.go b/dsl/things.go new file mode 100644 index 0000000..7badf4d --- /dev/null +++ b/dsl/things.go @@ -0,0 +1,119 @@ +package dsl + +import ( + "encoding/json" + "errors" + "io" + + "gopkg.in/resty.v1" + "gopkg.in/yaml.v2" +) + +// ConnectionInfo - used to describe the resource endpoints +type ConnectionInfo struct { + Authority string + Username string + Password string +} + +// Thing - nouns that h7t act on +type Thing interface { + Unmarshal(data []byte) error + Path() string + Count() int // no of components within a thing + InnerThings() []Thing // if Things is a aggregation of Things +} + +func unmarshal(data []byte, t interface{}) error { + if err := yaml.Unmarshal(data, t); err != nil { + if err := json.Unmarshal(data, t); err != nil { + return err + } + } + return nil +} + +// ReadThingFromFile - loads the file contents (yaml or json) into a thing, io.ReadFile passed so it can be UTed +func ReadThingFromFile(thing Thing, filename string, readfile func(filename string) ([]byte, error)) (err error) { + b, err := readfile(filename) + if err != nil { + return + } + err = thing.Unmarshal(b) + return +} + +// WriteThingToFile - marshsal a thing to yaml and write to file +func WriteThingToFile(thing Thing, fw io.Writer) (err error) { + b, err := yaml.Marshal(thing) + if err != nil { + return + } + _, err = fw.Write(b) + return +} + +// ExtractThingFromResource - Use the Things Path function to build a GET REST requests and unmarshal body to yaml +func ExtractThingFromResource(rc *resty.Client, thing Thing, ci ConnectionInfo) (err error) { + resp, err := rc.R(). + SetBasicAuth(ci.Username, ci.Password). + Get("https://" + ci.Authority + thing.Path()) + if err != nil { + return + } + err = thing.Unmarshal(resp.Body()) + return +} + +// PostThingToResource - Use the Things Path function to build a POST REST requests and marshal body +func PostThingToResource(rc *resty.Client, thing Thing, ci ConnectionInfo, shouldCommit bool) (err error) { + resp, err := rc.R(). + SetBasicAuth(ci.Username, ci.Password). + SetBody(thing). + Post("https://" + ci.Authority + thing.Path()) + if err != nil { + return + } + switch resp.StatusCode() { + case 200: + break + default: + return errors.New("Problem updating Thing: %v " + resp.String()) + } + if shouldCommit { + _, err = rc.R(). + SetBasicAuth(ci.Username, ci.Password). + SetBody(thing). + Post("https://" + ci.Authority + "/api/v1/configuration/") + if err != nil { + return + } + } + return +} + +// DeleteThingToResource - Use the Things Path function to build a DELETE REST request +func DeleteThingToResource(rc *resty.Client, thing Thing, ci ConnectionInfo, shouldCommit bool) (err error) { + resp, err := rc.R(). + SetBasicAuth(ci.Username, ci.Password). + Delete("https://" + ci.Authority + thing.Path()) + if err != nil { + return + } + switch resp.StatusCode() { + case 204: + break + default: + return errors.New("Problem deleting Thing: %v " + resp.String()) + } + if shouldCommit { + _, err = rc.R(). + SetBasicAuth(ci.Username, ci.Password). + SetBody(thing). + Post("https://" + ci.Authority + "/api/v1/configuration/") + if err != nil { + return + } + } + return +} diff --git a/dsl/things_test.go b/dsl/things_test.go new file mode 100644 index 0000000..375d9be --- /dev/null +++ b/dsl/things_test.go @@ -0,0 +1,292 @@ +package dsl + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + "path/filepath" + "strings" + "testing" + + "github.com/jszwec/csvutil" + + "github.com/jarcoal/httpmock" + "github.com/stretchr/testify/assert" + "gopkg.in/resty.v1" + "gopkg.in/yaml.v2" +) + +var ci = ConnectionInfo{ + Authority: "localhost:8080", + Username: "root", + Password: "Be1fast", +} + +var fakeDevices = Devices{ + Device: []Device{{ + DeviceID: "test-device", + Host: "10.0.0.1", + }}, +} + +var client = resty.New() + +type FakeReadFiler struct { + Str string +} + +// here's a fake ReadFile method that matches the signature of ioutil.ReadFile +func (f FakeReadFiler) ReadFile(filename string) ([]byte, error) { + buf := bytes.NewBufferString(f.Str) + return ioutil.ReadAll(buf) +} + +// HelperLoadBytes allows you to use relative path testdata directory as a place +// to load and store your data +func HelperLoadBytes(tb testing.TB, name string) []byte { + path := filepath.Join("testdata", name) // relative path + bytes, err := ioutil.ReadFile(path) // nolint : gosec + if err != nil { + tb.Fatal(err) + } + return bytes +} + +func TestCsvMarshalUnMarshal(t *testing.T) { + b, err := csvutil.Marshal(fakeDevices.Device) + assert.Nil(t, err, "Failed to marshal csv representation of Devices") + assert.Equal(t, "device-id,host,username,password\ntest-device,10.0.0.1,,\n", string(b), "Should only contain device id, host, uname and password") + var d []Device + err = csvutil.Unmarshal([]byte("device-id,host,username,password\ntest-device,10.0.0.1,,\n"), &d) + assert.Nil(t, err, "Failed to unmarshal csv representation of Devices") + assert.Nil(t, d[0].SystemID, "System ID should be nil") + assert.Nil(t, d[0].IAgent, "IAgent should be nil") + assert.Equal(t, "test-device", d[0].DeviceID, "Should only contain device id, host, uname and password") + + w := &bytes.Buffer{} + _ = WriteThingToFile(&fakeDevices, w) + assert.NotContains(t, w.String(), "password", "should not contain password") + +} + +func TestUnmarshalFailure(t *testing.T) { + var devices Devices + err := devices.Unmarshal([]byte{45}) + assert.EqualError(t, err, "invalid character ' ' in numeric literal", "should have generated an error") +} + +func TestDeviceYamlParsing(t *testing.T) { + var devices Devices + err := devices.Unmarshal(HelperLoadBytes(t, "./devices.yml")) + assert.Nil(t, err, "Failed to parse yaml representation of Devices") + assert.Len(t, devices.Device, 3, "Expected to parse 3 Devices") + assert.EqualValues(t, "4200_1", devices.Device[0].DeviceID, "Yaml type with a hyphen, didn't decode correctly") +} + +func TestDeviceOmit(t *testing.T) { + var devices Devices + _ = devices.Unmarshal(HelperLoadBytes(t, "./devices.yml")) + maxDevice, err := yaml.Marshal(devices.Device[0]) + if err != nil { + assert.Nil(t, err, "Failed to marshal devices to yaml") + } + assert.NotContains(t, string(maxDevice), "cisco", "cisco should be ignored") + + minDevice, err := json.Marshal(devices.Device[2]) + if err != nil { + assert.Nil(t, err, "Failed to marshal devices to json") + } + assert.NotContains(t, string(minDevice), "authentication", "Optional Authentication type was not ignored") + assert.NotContains(t, string(minDevice), "iAgent", "Optional NETCONF type was not ignored") + assert.NotContains(t, string(minDevice), "open-config", "Optional Open Config type was not ignored") + assert.NotContains(t, string(minDevice), "snmp", "Optional SNMP type was not ignored") + assert.NotContains(t, string(minDevice), "vendor", "Optional vendor type was not ignored") + partialDevice, err := json.Marshal(devices.Device[1]) + if err != nil { + assert.Nil(t, err, "Failed to marshal devices to json") + } + assert.NotContains(t, string(partialDevice), "v2", "Optional Community type was not ignored") + assert.NotContains(t, string(partialDevice), "juniper", "Optional Vendor juniper was not ignored") +} + +func TestWriteThingToFile(t *testing.T) { + devices := Devices{ + Device: []Device{{ + DeviceID: "localhost", + }}, + } + type args struct { + thing Thing + } + tests := []struct { + name string + args args + contains string + wantErr bool + }{ + {"test writing a yaml thing", args{thing: &devices}, "device-id: localhost", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fw := &bytes.Buffer{} + if err := WriteThingToFile(tt.args.thing, fw); (err != nil) != tt.wantErr { + t.Errorf("WriteThingToFile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotFw := fw.String(); !strings.Contains(gotFw, tt.contains) { + t.Errorf("WriteThingToFile() = %v, want %v", gotFw, tt.contains) + } + }) + } +} + +func TestExtractThingFromResource(t *testing.T) { + + // Get the underlying HTTP Client and set it to Mock + httpmock.ActivateNonDefault(client.GetClient()) + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("GET", "https://localhost:8080/api/v1/devices/", + func(req *http.Request) (*http.Response, error) { + resp, err := httpmock.NewJsonResponse(200, Devices{}) + if err != nil { + return httpmock.NewStringResponse(500, ""), nil + } + return resp, nil + }, + ) + + thing := Devices{} + err := ExtractThingFromResource(client, &thing, ci) + assert.Nil(t, err, "response from ExtractThingFromResource should be valid") + + assert.True(t, httpmock.GetTotalCallCount() == 1, "Expected Single call to Resource") + + _, isPresent := httpmock.GetCallCountInfo()["GET https://localhost:8080/api/v1/devices/"] + assert.True(t, isPresent, "Should contain a correctly formatted GET") + +} + +func TestReadThingFromFile(t *testing.T) { + b, _ := yaml.Marshal(fakeDevices) + fake := FakeReadFiler{Str: string(b)} + + type args struct { + thing Thing + filename string + readfile func(filename string) ([]byte, error) + } + tests := []struct { + name string + args args + wantErr bool + }{ + {"Ensure valid Devices are returned", + args{thing: &Devices{}, filename: "devices.yml", readfile: fake.ReadFile}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := ReadThingFromFile(tt.args.thing, tt.args.filename, tt.args.readfile); (err != nil) != tt.wantErr { + t.Errorf("ReadThingFromFile() error = %v, wantErr %v", err, tt.wantErr) + assert.Len(t, tt.args.thing.(*Devices).Device, 1) + } + }) + } +} + +func TestPostThingToResource(t *testing.T) { + + // Get the underlying HTTP Client and set it to Mock + httpmock.ActivateNonDefault(client.GetClient()) + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("POST", "https://localhost:8080/api/v1/devices/", + httpmock.NewStringResponder(200, ``)) + + httpmock.RegisterResponder("POST", "https://localhost:8080/api/v1/configuration/", + httpmock.NewStringResponder(200, ``)) + + type args struct { + rc *resty.Client + thing Thing + ci ConnectionInfo + shouldCommit bool + } + tests := []struct { + name string + args args + wantErr bool + }{ + {"valid post thing to resource, no commit", args{rc: client, thing: &fakeDevices, ci: ci, shouldCommit: false}, false}, + {"valid post thing to resource and commit", args{rc: client, thing: &fakeDevices, ci: ci, shouldCommit: true}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := PostThingToResource(tt.args.rc, tt.args.thing, tt.args.ci, tt.args.shouldCommit); (err != nil) != tt.wantErr { + t.Errorf("PostThingToResource() error = %v, wantErr %v", err, tt.wantErr) + + if !tt.args.shouldCommit { + assert.Len(t, httpmock.GetTotalCallCount(), 1, "Expected Single call to Resource") + } + + _, isPresent := httpmock.GetCallCountInfo()["POST https://localhost:8080/api/v1/devices/"] + assert.True(t, isPresent, "Should contain a correctly formatted POST to devices") + + if tt.args.shouldCommit { + _, isPresent := httpmock.GetCallCountInfo()["POST https://localhost:8080/api/v1/configuration/"] + assert.True(t, isPresent, "Should contain a correctly formatted POST to configuration") + } + } + }) + } +} + +func TestDeleteThingToResource(t *testing.T) { + + // Get the underlying HTTP Client and set it to Mock + httpmock.ActivateNonDefault(client.GetClient()) + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("DELETE", "https://localhost:8080/api/v1/device/test-device/", + httpmock.NewStringResponder(204, ``)) + + httpmock.RegisterResponder("POST", "https://localhost:8080/api/v1/configuration/", + httpmock.NewStringResponder(200, ``)) + + type args struct { + rc *resty.Client + thing Thing + ci ConnectionInfo + shouldCommit bool + } + tests := []struct { + name string + args args + wantErr bool + }{ + {"valid delete thing to resource, no commit", args{rc: client, thing: &fakeDevices.Device[0], ci: ci, shouldCommit: false}, false}, + {"valid delete thing to resource and commit", args{rc: client, thing: &fakeDevices.Device[0], ci: ci, shouldCommit: true}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := DeleteThingToResource(tt.args.rc, tt.args.thing, tt.args.ci, tt.args.shouldCommit); (err != nil) != tt.wantErr { + t.Errorf("DeleteThingToResource() error = %v, wantErr %v", err, tt.wantErr) + + if !tt.args.shouldCommit { + assert.Len(t, httpmock.GetTotalCallCount(), 1, "Expected Single call to Resource") + } + + _, isPresent := httpmock.GetCallCountInfo()["DELETE https://localhost:8080/api/v1/devices/test-device/"] + assert.True(t, isPresent, "Should contain a correctly formatted POST") + + if tt.args.shouldCommit { + _, isPresent := httpmock.GetCallCountInfo()["POST https://localhost:8080/api/v1/configuration/"] + assert.True(t, isPresent, "Should contain a correctly formatted POST to configuration") + } + } + }) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d726202 --- /dev/null +++ b/go.mod @@ -0,0 +1,26 @@ +module github.com/damianoneill/h7t + +go 1.13 + +require ( + github.com/damianoneill/net v0.1.2 + github.com/golangci/golangci-lint v1.21.0 + github.com/goreleaser/goreleaser v0.121.0 + github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd + github.com/hashicorp/go-plugin v1.0.1 + github.com/jarcoal/httpmock v1.0.4 + github.com/jszwec/csvutil v1.2.1 + github.com/mattn/go-runewidth v0.0.5 // indirect + github.com/mitchellh/go-homedir v1.1.0 + github.com/olekukonko/tablewriter v0.0.2 + github.com/satori/go.uuid v1.2.0 // indirect + github.com/spf13/afero v1.2.2 + github.com/spf13/cobra v0.0.5 + github.com/spf13/viper v1.5.0 + github.com/stretchr/testify v1.4.0 + github.com/tj/assert v0.0.0-20171129193455-018094318fb0 + golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc + golang.org/x/lint v0.0.0-20190409202823-959b441ac422 + gopkg.in/resty.v1 v1.12.0 + gopkg.in/yaml.v2 v2.2.5 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..12545e0 --- /dev/null +++ b/go.sum @@ -0,0 +1,587 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.39.0 h1:UgQP9na6OTfp4dsAiz/eFpFA1C6tPdH5wiRdi19tuMw= +cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= +code.gitea.io/sdk/gitea v0.0.0-20191013013401-e41e9ea72caa h1:KgpwNF1StxPXMfCD9M++jvCUPUqHPAbuvQn1q3sWtqw= +code.gitea.io/sdk/gitea v0.0.0-20191013013401-e41e9ea72caa/go.mod h1:8IxkM1gyiwEjfO0m47bcmr3u3foR15+LoVub43hCHd0= +contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= +contrib.go.opencensus.io/exporter/ocagent v0.5.0 h1:TKXjQSRS0/cCDrP7KvkgU6SmILtF/yV2TOs/02K/WZQ= +contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= +contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= +contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= +contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= +github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU= +github.com/Azure/azure-pipeline-go v0.1.8/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= +github.com/Azure/azure-pipeline-go v0.1.9 h1:u7JFb9fFTE6Y/j8ae2VK33ePrRqJqoCM/IWkQdAZ+rg= +github.com/Azure/azure-pipeline-go v0.1.9/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= +github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v30.1.0+incompatible h1:HyYPft8wXpxMd0kfLtXo6etWcO+XuPbLkcgx9g2cqxU= +github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0= +github.com/Azure/azure-storage-blob-go v0.6.0 h1:SEATKb3LIHcaSIX+E6/K4kJpwfuozFEsmt5rS56N6CE= +github.com/Azure/azure-storage-blob-go v0.6.0/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y= +github.com/Azure/go-autorest v12.0.0+incompatible h1:N+VqClcomLGD/sHb3smbSYYtNMgKpVV3Cd5r5i8z6bQ= +github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190605020000-c4ba1fdf4d36/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo= +github.com/Masterminds/semver/v3 v3.0.1 h1:2kKm5lb7dKVrt5TYUiAavE6oFc1cFT0057UVGT+JqLk= +github.com/Masterminds/semver/v3 v3.0.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us= +github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/apex/log v1.1.1 h1:BwhRZ0qbjYtTob0I+2M+smavV0kOC8XgcnGZcyL9liA= +github.com/apex/log v1.1.1/go.mod h1:Ls949n1HFtXfbDcjiTTFQqkVUrte0puoIBfO3SVgwOA= +github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= +github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.25.11 h1:wUivbsVOH3LpHdC3Rl5i+FLHfg4sOmYgv4bvHe7+/Pg= +github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/bombsimon/wsl v1.2.5 h1:9gTOkIwVtoDZywvX802SDHokeX4kW1cKnV8ZTVAPkRs= +github.com/bombsimon/wsl v1.2.5/go.mod h1:43lEF/i0kpXbLCeDXL9LMT8c92HyBywXb0AsgMHYngM= +github.com/caarlos0/ctrlc v1.0.0 h1:2DtF8GSIcajgffDFJzyG15vO+1PuBWOMUdFut7NnXhw= +github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= +github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e h1:V9a67dfYqPLAvzk5hMQOXYJlZ4SLIXgyKIE+ZiHzgGQ= +github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= +github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc= +github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= +github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4= +github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/damianoneill/net v0.1.2 h1:hZ25QH7cgH/Y/Pm4Dyuz1Ux1ycIJROqDUfuwUxMa1Q0= +github.com/damianoneill/net v0.1.2/go.mod h1:/bUc5RJ/zTC/xuHRBsYcy744Ehhyic2LYF/jDLn8ERE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db h1:GYXWx7Vr3+zv833u+8IoXbNnQY0AdXsxAgI0kX7xcwA= +github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0= +github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= +github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= +github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/go-toolsmith/pkgload v1.0.0 h1:4DFWWMXVfbcN5So1sBNW9+yeiMqLFGl1wFLTL5R0Tgg= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0 h1:zKymWyA1TRYvqYrYDrfEMZULyrhcnGY3x7LDKU2XQaA= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b h1:ekuhfTjngPhisSjOJ0QWKpPQE8/rbknHaes6WVJj5Hw= +github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee h1:J2XAy40+7yz70uaOiMbNnluTg7gyQhtGqLQncQh+4J8= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.21.0 h1:HxAxpR8Z0M8omihvQdsD3PF0qPjlqYqp2vMJzstoKeI= +github.com/golangci/golangci-lint v1.21.0/go.mod h1:phxpHK52q7SE+5KpPnti4oZTdFCEsn/tKN+nFvCKXfk= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0 h1:HVfrLniijszjS1aiNg8JbBMO2+E1WIQ+j/gL4SQqGPg= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= +github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-replayers/grpcreplay v0.1.0 h1:eNb1y9rZFmY4ax45uEEECSa8fsxGRU+8Bil52ASAwic= +github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= +github.com/google/go-replayers/httpreplay v0.1.0 h1:AX7FUb4BjrrzNvblr/OlgwrmFiep6soj5K2QSDW7BGk= +github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible h1:xmapqc1AyLoB+ddYT6r04bD9lIjlOqGaREovi0SzFaE= +github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/rpmpack v0.0.0-20191101142923-13d81472ccfe h1:P1WflKHEgTAYe39btxYzeds84DhxQSLj4hfoNn0tCyQ= +github.com/google/rpmpack v0.0.0-20191101142923-13d81472ccfe/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.3.0 h1:imGQZGEVEHpje5056+K+cgdO72p0LQv2xIIFXNGUf60= +github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s= +github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= +github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/goreleaser/goreleaser v0.121.0 h1:CeOa4lqMI1TlPXral4PeMxWoRQv+fq0JTPUUVsl+P4Y= +github.com/goreleaser/goreleaser v0.121.0/go.mod h1:Acn33/psgsNrPClBfaYjaqmtfK4ilOIWQWYEls6nOPs= +github.com/goreleaser/nfpm v1.1.5 h1:bjVIflQl0b3lNQvO/b++cKMKXW+INL70AdGR+zDXWps= +github.com/goreleaser/nfpm v1.1.5/go.mod h1:4V1IQlCSizz8KHXBb0C82xIh/NNJdLo332q1T4r909U= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.2 h1:S+ef0492XaIknb8LMjcwgW2i3cNTzDYMmDrOThOJNWc= +github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd h1:rNuUHR+CvK1IS89MMtcF0EpcVMZtjKfPRp4MEmt/aTs= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jarcoal/httpmock v1.0.4 h1:jp+dy/+nonJE4g4xbVtl9QdrUNbn6/3hDT5R4nDIZnA= +github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jszwec/csvutil v1.2.1 h1:9+vmGqMdYxIbeDmVbTrVryibx2izwHAfKdPwl4GPNHM= +github.com/jszwec/csvutil v1.2.1/go.mod h1:8YHz6C3KVdIeCxLMvwbbIVDCTA/Wi2df93AZlQNaE2U= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kamilsk/retry/v4 v4.3.1 h1:hNQmK1xAgybAVsadNAGvCNutFLS2h+Ycpw317u4d+i0= +github.com/kamilsk/retry/v4 v4.3.1/go.mod h1:VaCDWufu3zVv7Ktt+Z7Dcslli2te4QEZoqt4QR7xgQg= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb h1:RHba4YImhrUVQDHUCe2BNSOz4tVy2yGyXhvYDvxGgeE= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.5 h1:jrGtp51JOKTWgvLFzfG6OtZOJcK2sEnzc/U+zw7TtbA= +github.com/mattn/go-runewidth v0.0.5/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY= +github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2 h1:sq53g+DWf0J6/ceFUHpQ0nAEb6WgM++fq16MZ91cS6o= +github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b h1:+gCnWOZV8Z/8jehJ2CdqB47Z3S+SREmQcuXkRFLNsiI= +github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d h1:BzRvVq1EHuIjxpijCEKpAxzKUUMurOQ4sknehIATRh8= +github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e h1:MZM7FHLqUHYI0Y/mQAt3d2aYa0SiNms/hFqC9qJYolM= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= +github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs= +github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4= +github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/tj/assert v0.0.0-20171129193455-018094318fb0 h1:Rw8kxzWo1mr6FSaYXjQELRe88y2KdfynXdnK72rdjtA= +github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= +github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= +github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= +github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ultraware/funlen v0.0.2 h1:Av96YVBwwNSe4MLR7iI/BIa3VyI7/djnto/pK3Uxbdo= +github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg= +github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= +github.com/uudashr/gocognit v0.0.0-20190926065955-1655d0de0517 h1:ChMKTho2hWKpks/nD/FL2KqM1wuVt62oJeiE8+eFpGs= +github.com/uudashr/gocognit v0.0.0-20190926065955-1655d0de0517/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/xanzy/go-gitlab v0.21.0 h1:Ru55sR4TBoDNsAKwCOpzeaGtbiWj7xTksVmzBJbLu6c= +github.com/xanzy/go-gitlab v0.21.0/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +gocloud.dev v0.17.0 h1:UuDiCphYsiNhRNLtgHVL/eZheQeCt00hL3XjDfbt820= +gocloud.dev v0.17.0/go.mod h1:tIHTRdR1V5dlD8sTkzYdTGizBJ314BDykJ8KmadEXwo= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4= +golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 h1:N66aaryRB3Ax92gH0v3hp1QYZ3zWWCCUR/j8Ifh45Ss= +golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 h1:rOhMmluY6kLMhdnrivzec6lLgaVbMHMn2ISQXJeJ5EM= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff h1:XdBG6es/oFDr1HwaxkxgVve7NB281QhxgK/i4voubFs= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.6.0 h1:2tJEkRfnZL5g1GeBUlITh/rqT5HG3sFcoVCUUxmgJ2g= +google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601 h1:9VBRTdmgQxbs6HE0sUnMrSWNePppAJU07NYvX5dIB04= +google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= +pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4 h1:JPJh2pk3+X4lXAkZIk2RuE/7/FoK9maXw+TNPJhVS/c= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/main.go b/main.go new file mode 100644 index 0000000..519ff64 --- /dev/null +++ b/main.go @@ -0,0 +1,13 @@ +package main + +import "github.com/damianoneill/h7t/cmd" + +var ( + version = "dev" + commit = "none" + date = "unknown" +) + +func main() { + cmd.Execute(version, commit, date) +} diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..81da863 --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,34 @@ +# Transforms + +Customer data is available in lots of different formats. The tool provides a plugin loading mechanism for transforming that data into the dsl format used by the tool. + +This provides an extension mechanism for writing customer plugins for doing the conversion. + +The plugin technology is based on hashicorp [go-plugin](https://github.com/hashicorp/go-plugin) a RPC based solution for Go plugins. +An interface is defined that plugin authors must implement: + +```go +// Arguments - composite for passing data across net/rpc +type Arguments struct { + InputDirectory string + CmdLineArgs []string +} + +// Transformer is the interface that must be implemented by plugin authors. +type Transformer interface { + Devices(args Arguments) (dsl.Devices, error) +} +``` + +## CSV Transform + +The sample plugin provides a transform for mapping CSV files into the Device dsl required by Healthbot. + +The plugin expects to parse CSV files in the following format: + +```csv +device-id,host,username,password +mx1,10.0.0.1,root,changeme +mx2,10.0.0.2,root,"changeme now" +mx3,10.0.0.3,, +``` \ No newline at end of file diff --git a/plugins/csv/main.go b/plugins/csv/main.go new file mode 100644 index 0000000..8e916bb --- /dev/null +++ b/plugins/csv/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/jszwec/csvutil" + + "github.com/damianoneill/h7t/dsl" + "github.com/damianoneill/h7t/plugins" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" +) + +// CsvDevices - is a implementation of Transformer +type CsvDevices struct { + logger hclog.Logger +} + +// ReadCsvFile - using csvutil to unmarshal, it expects a slice of types +func ReadCsvFile(filename string, readfile func(filename string) ([]byte, error)) (devices []dsl.Device, err error) { + b, err := readfile(filename) + if err != nil { + return + } + err = csvutil.Unmarshal(b, &devices) + return +} + +// Devices - returns a list of dsl Devices +func (g *CsvDevices) Devices(args plugins.Arguments) (devices dsl.Devices, err error) { + g.logger.Debug("args:", "inputDirectory", args.InputDirectory) + + // can be used by plugin providers to handle arguments + g.logger.Debug("args:", "command line arguments", args.CmdLineArgs) + + // get all files in the input directory + files, err := filepath.Glob(args.InputDirectory + string(filepath.Separator) + "*") + if err != nil { + return + } + + for _, filename := range files { + var d []dsl.Device + d, err = ReadCsvFile(filename, ioutil.ReadFile) + if err != nil { + return + } + devices.Device = append(devices.Device, d...) + } + + return +} + +var handshakeConfig = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "BASIC_PLUGIN", + MagicCookieValue: "csv", +} + +func main() { + logger := hclog.New(&hclog.LoggerOptions{ + Level: hclog.Trace, + Output: os.Stderr, + JSONFormat: true, + }) + + transformer := &CsvDevices{ + logger: logger, + } + + // pluginMap is the map of plugins we can dispense. + var pluginMap = map[string]plugin.Plugin{ + "transformer": &plugins.TransformerPlugin{Impl: transformer}, + } + + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: handshakeConfig, + Plugins: pluginMap, + }) +} diff --git a/plugins/csv/main_test.go b/plugins/csv/main_test.go new file mode 100644 index 0000000..50de1ee --- /dev/null +++ b/plugins/csv/main_test.go @@ -0,0 +1,55 @@ +package main + +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/tj/assert" + + "github.com/damianoneill/h7t/dsl" +) + +type FakeReadFiler struct { + Str string +} + +// here's a fake ReadFile method that matches the signature of ioutil.ReadFile +func (f FakeReadFiler) ReadFile(filename string) ([]byte, error) { + buf := bytes.NewBufferString(f.Str) + return ioutil.ReadAll(buf) +} + +func TestReadCsvFile(t *testing.T) { + + fake := FakeReadFiler{Str: "device-id,host,username,password\nmx1,10.0.0.1,,"} + + type args struct { + filename string + readfile func(filename string) ([]byte, error) + } + tests := []struct { + name string + args args + wantDevices []dsl.Device + wantErr bool + }{ + { + name: "Test csv parsed correctly from file", + args: args{filename: "filename", readfile: fake.ReadFile}, + wantDevices: []dsl.Device{dsl.Device{DeviceID: "mx1", Host: "10.0.0.1"}}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotDevices, err := ReadCsvFile(tt.args.filename, tt.args.readfile) + if (err != nil) != tt.wantErr { + t.Errorf("ReadCsvFile() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, gotDevices[0].Host, tt.wantDevices[0].Host, "Host value should be the same") + assert.Equal(t, gotDevices[0].DeviceID, tt.wantDevices[0].DeviceID, "Device ID value should be the same") + }) + } +} diff --git a/plugins/transformer_interface.go b/plugins/transformer_interface.go new file mode 100644 index 0000000..8e2bc36 --- /dev/null +++ b/plugins/transformer_interface.go @@ -0,0 +1,56 @@ +package plugins + +import ( + "net/rpc" + + "github.com/damianoneill/h7t/dsl" + "github.com/hashicorp/go-plugin" +) + +// Arguments - composite for passing data across net/rpc +type Arguments struct { + InputDirectory string + CmdLineArgs []string +} + +// Transformer is the interface that must be implemented by plugin authors. +type Transformer interface { + Devices(args Arguments) (dsl.Devices, error) +} + +// TransformerRPC - an implementation that talks over RPC +type TransformerRPC struct{ client *rpc.Client } + +// Devices - interface implementation +func (g *TransformerRPC) Devices(args Arguments) (resp dsl.Devices, err error) { + err = g.client.Call("Plugin.Devices", args, &resp) + return +} + +// TransformerRPCServer - is the RPC server that TransformerRPC talks to, conforming to +// the requirements of net/rpc +type TransformerRPCServer struct { + Impl Transformer +} + +// Devices - Server implementation +func (s *TransformerRPCServer) Devices(args Arguments, resp *dsl.Devices) (err error) { + *resp, err = s.Impl.Devices(args) + return +} + +// TransformerPlugin is the implementation of plugin.Devices so we can serve/consume this +type TransformerPlugin struct { + // Impl Injection + Impl Transformer +} + +// Server - muxing +func (p *TransformerPlugin) Server(*plugin.MuxBroker) (interface{}, error) { + return &TransformerRPCServer{Impl: p.Impl}, nil +} + +// Client - muxing +func (TransformerPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { + return &TransformerRPC{client: c}, nil +} diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..4ba3450 --- /dev/null +++ b/tools.go @@ -0,0 +1,17 @@ +// +build tools + +package main + +import ( + _ "github.com/golangci/golangci-lint/cmd/golangci-lint" + _ "github.com/goreleaser/goreleaser" + _ "github.com/spf13/cobra/cobra" + _ "golang.org/x/lint/golint" + // https://github.com/src-d/proteus + // _ "github.com/golang/protobuf/protoc-gen-go" + // _ "github.com/gogo/protobuf/..." + // https://github.com/anjmao/go2proto + // _ "github.com/anjmao/go2proto" + // gRPC testing + // _ "github.com/fullstorydev/grpcui/cmd/grpcui" +)