├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── cmd └── gosal │ └── gosal.go ├── config └── config.go ├── go.mod ├── go.sum ├── sal ├── sal.go └── sal_test.go ├── version ├── version.go └── version_test.go └── xpreports ├── build_darwin.go ├── build_linux.go ├── build_windows.go ├── cm ├── cm.go └── cm_test.go ├── linux ├── computersystem.go ├── processor.go ├── rootvolume.go ├── serial.go └── users.go ├── reports.go └── windows ├── win32_bios.go ├── win32_computersystem.go ├── win32_logicaldisk.go ├── win32_os.go └── win32_processor.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | gosal.exe 3 | gosal 4 | !cmd/gosal 5 | build/ 6 | vendor/ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | os: 3 | - linux 4 | - windows 5 | 6 | go: 7 | - 1.13.x 8 | 9 | env: GO111MODULE=on 10 | 11 | script: 12 | - go test -v ./... 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build 2 | 3 | .PHONY: build 4 | 5 | ifndef ($(GOPATH)) 6 | GOPATH = $(HOME)/go 7 | endif 8 | 9 | PATH := $(GOPATH)/bin:$(PATH) 10 | VERSION = $(shell git describe --tags --always --dirty) 11 | BRANCH = $(shell git rev-parse --abbrev-ref HEAD) 12 | REVISION = $(shell git rev-parse HEAD) 13 | REVSHORT = $(shell git rev-parse --short HEAD) 14 | USER = $(shell whoami) 15 | APP_NAME = gosal 16 | PKGDIR_TMP = ${TMPDIR}golang 17 | 18 | ifneq ($(OS), Windows_NT) 19 | CURRENT_PLATFORM = linux 20 | # If on macOS, set the shell to bash explicitly 21 | ifeq ($(shell uname), Darwin) 22 | SHELL := /bin/bash 23 | CURRENT_PLATFORM = darwin 24 | endif 25 | 26 | # To populate version metadata, we use unix tools to get certain data 27 | GOVERSION = $(shell go version | awk '{print $$3}') 28 | NOW = $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") 29 | else 30 | CURRENT_PLATFORM = windows 31 | 32 | # To populate version metadata, we use windows tools to get the certain data 33 | GOVERSION_CMD = "(go version).Split()[2]" 34 | GOVERSION = $(shell powershell $(GOVERSION_CMD)) 35 | NOW = $(shell powershell Get-Date -format s) 36 | endif 37 | 38 | BUILD_VERSION = "\ 39 | -X github.com/airbnb/gosal/version.appName=${APP_NAME} \ 40 | -X github.com/airbnb/gosal/version.version=${VERSION} \ 41 | -X github.com/airbnb/gosal/version.branch=${BRANCH} \ 42 | -X github.com/airbnb/gosal/version.buildUser=${USER} \ 43 | -X github.com/airbnb/gosal/version.buildDate=${NOW} \ 44 | -X github.com/airbnb/gosal/version.revision=${REVISION} \ 45 | -X github.com/airbnb/gosal/version.goVersion=${GOVERSION}" 46 | 47 | define HELP_TEXT 48 | 49 | Makefile commands 50 | 51 | make deps - Install dependent programs and libraries 52 | make clean - Delete all build artifacts 53 | 54 | make build - Build the code 55 | 56 | make test - Run the Go tests 57 | make lint - Run the Go linters 58 | 59 | endef 60 | 61 | help: 62 | $(info $(HELP_TEXT)) 63 | 64 | deps: 65 | GO111MODULE=on go mod download 66 | GO111MODULE=on go mod verify 67 | 68 | clean: 69 | rm -rf build/ 70 | rm -f *.zip 71 | rm -rf ${PKGDIR_TMP}_darwin 72 | rm -rf ${PKGDIR_TMP}_linux 73 | rm -rf ${PKGDIR_TMP}_windows 74 | 75 | .pre-build: 76 | mkdir -p build/darwin 77 | mkdir -p build/linux 78 | mkdir -p build/windows 79 | 80 | 81 | build: .pre-build 82 | GOOS=darwin go build -i -o build/darwin/${APP_NAME} -pkgdir ${PKGDIR_TMP}_darwin -ldflags ${BUILD_VERSION} ./cmd/gosal 83 | GOOS=linux go build -i -o build/linux/${APP_NAME} -pkgdir ${PKGDIR_TMP}_linux -ldflags ${BUILD_VERSION} ./cmd/gosal 84 | GOOS=windows go build -i -o build/windows/${APP_NAME}.exe -pkgdir ${PKGDIR_TMP}_windows -ldflags ${BUILD_VERSION} ./cmd/gosal 85 | 86 | 87 | test: 88 | go test -cover -race -v $(shell go list ./... | grep -v /vendor/) 89 | 90 | lint: 91 | @if gofmt -l . | egrep -v ^vendor/ | grep .go; then \ 92 | echo "^- Repo contains improperly formatted go files; run gofmt -w *.go" && exit 1; \ 93 | else echo "All .go files formatted correctly"; fi 94 | go vet ./... 95 | # Bandaid until https://github.com/golang/lint/pull/325 is merged 96 | # TODO fix path to golint 97 | # golint -set_exit_status `go list ./... | grep -v /vendor/` 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gosal (sal-client) 2 | 3 | _Due to major changes in Sal4's checkin method, people who run Sal3 should checkout the Sal3 branch and build from there._ 4 | 5 | ## Overview 6 | 7 | Gosal is intended to be a multi platform client for sal, Gosal does not provide a complete 1-2-1 mapping of reporting info compared to Sal for macOS PRs are welcome. 8 | 9 | ## Getting Started 10 | 11 | Your configuration file should be `json` formatted as follows: 12 | 13 | ```json 14 | { 15 | "key": "your gigantic machine group key", 16 | "url": "https://urltoyourserver.com", 17 | "management": { 18 | "tool": "puppet", 19 | "path": "C:\\Program Files\\Puppet Labs\\Puppet\\bin\\puppet.bat", 20 | "command": "facts" 21 | } 22 | } 23 | ``` 24 | 25 | # Running gosal 26 | 27 | Gosal requires the configuration file to be passed in as an argument like so... 28 | 29 | #### Windows Example 30 | 31 | `gosal.exe --config "C:\path\to\config.json"` 32 | 33 | # Building Sal3 34 | 35 | To build the project after cloning: 36 | 37 | ``` 38 | make deps 39 | make build 40 | ``` 41 | 42 | Gosal OS specific binaries will be added to the `build/` directory. 43 | 44 | # Building Sal4 45 | 46 | Sal4 (master) is switching to go.mod! 47 | 48 | ## Dependencies 49 | 50 | Gosal uses [go-modules](https://blog.golang.org/using-go-modules) to manage external dependencies. Run `make deps` to install/update the required dependencies. 51 | After adding a new dependency, run `mod verify` & `go mod tidy`, which will update the mod.go & mod.sum file. 52 | 53 | ## Formatting your code 54 | 55 | Go has an exceptional formatter - please use it! 56 | 57 | ``` 58 | gofmt -s -w *.go 59 | gofmt -s -w ./*/*.go 60 | ``` 61 | -------------------------------------------------------------------------------- /cmd/gosal/gosal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/pkg/errors" 8 | "github.com/spf13/cobra" 9 | 10 | "github.com/airbnb/gosal/config" 11 | "github.com/airbnb/gosal/sal" 12 | "github.com/airbnb/gosal/version" 13 | ) 14 | 15 | func createVersionCmd() *cobra.Command { 16 | var fFull bool 17 | versionCmd := &cobra.Command{ 18 | Use: "version", 19 | Short: "Print the version number of gosal", 20 | Long: `Print the version number and build information of gosal`, 21 | Run: func(cmd *cobra.Command, args []string) { 22 | if fFull { 23 | version.PrintFull() 24 | return 25 | } 26 | version.Print() 27 | }, 28 | } 29 | 30 | versionCmd.PersistentFlags().BoolVar(&fFull, "full", false, "print full version information") 31 | 32 | return versionCmd 33 | } 34 | 35 | func createRootCmd(conf *config.Config) *cobra.Command { 36 | // rootCmd represents the base command when called without any subcommands 37 | rootCmd := &cobra.Command{ 38 | Use: "gosal", 39 | Short: "gosal uploads machine details to sal", 40 | Long: `Gosal is intended to be a multi platform client for sal. 41 | 42 | Complete documentation is available at https://github.com/airbnb/gosal/.`, 43 | Run: func(cmd *cobra.Command, args []string) { 44 | if !conf.Loaded() { 45 | fatal(errors.New("config file not loaded. Must specify --config flag")) 46 | } 47 | if err := sal.SendCheckin(conf); err != nil { 48 | fatal(err) 49 | } 50 | }, 51 | } 52 | 53 | rootCmd.PersistentFlags().StringP("config", "c", "", "Path to a configuration file") 54 | 55 | return rootCmd 56 | } 57 | 58 | func loadConfig(cmd *cobra.Command, conf *config.Config) error { 59 | configFile := cmd.PersistentFlags().Lookup("config").Value.String() 60 | if configFile == "" { 61 | // no config file specified. Must not be required: (example: gosal version). 62 | return nil 63 | } 64 | loaded, err := config.New(configFile) 65 | // copy the value of loaded to conf. 66 | // because go arguments are passed by value (copied) we cannot replace the entire 67 | // struct here. Only modify its values. 68 | *conf = *loaded 69 | return errors.Wrapf(err, "loading config file %s", configFile) 70 | } 71 | 72 | func fatal(err error) { 73 | fmt.Printf("gosal did not complete: %s\n", err) 74 | os.Exit(1) 75 | } 76 | 77 | func main() { 78 | // create root command and load config. 79 | conf := new(config.Config) 80 | rootCmd := createRootCmd(conf) 81 | rootCmd.ParseFlags(os.Args) 82 | if err := loadConfig(rootCmd, conf); err != nil { 83 | fatal(err) 84 | } 85 | 86 | // add additional commands to root 87 | rootCmd.AddCommand(createVersionCmd()) 88 | 89 | // run the root command. 90 | if err := rootCmd.Execute(); err != nil { 91 | fatal(err) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | ) 8 | 9 | // New creates a Config from a JSON file. 10 | func New(path string) (*Config, error) { 11 | file, err := ioutil.ReadFile(path) 12 | if err != nil { 13 | return nil, fmt.Errorf("config: opening client config file: %s", err) 14 | } 15 | 16 | var conf Config 17 | if err = json.Unmarshal(file, &conf); err != nil { 18 | return nil, fmt.Errorf("config: unmarshal config json: %s", err) 19 | } 20 | conf.loaded = true 21 | return &conf, nil 22 | } 23 | 24 | // Config is Sal client config. 25 | type Config struct { 26 | Key string 27 | URL string 28 | 29 | Management *Management 30 | 31 | loaded bool 32 | } 33 | 34 | // Loaded verifies if the struct was created by the LoadConfig struct. 35 | func (c *Config) Loaded() bool { 36 | return c.loaded 37 | } 38 | 39 | // Management is the nested config 40 | type Management struct { 41 | Tool string 42 | Path string 43 | Command string 44 | } 45 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/airbnb/gosal 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 7 | github.com/dselans/dmidecode v0.0.0-20180814053009-65c3f9d81910 8 | github.com/go-ole/go-ole v1.2.4 // indirect 9 | github.com/kr/pretty v0.1.0 // indirect 10 | github.com/pkg/errors v0.8.1 11 | github.com/shirou/gopsutil v2.19.10+incompatible 12 | github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect 13 | github.com/spf13/cobra v0.0.5 14 | github.com/stretchr/testify v1.4.0 // indirect 15 | golang.org/x/sys v0.1.0 // indirect 16 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= 3 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= 4 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 5 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 6 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 7 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 8 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/dselans/dmidecode v0.0.0-20180814053009-65c3f9d81910 h1:w9T/rS5VP0SPXqYr7BpUeqf6ukp/GEWm6nXhziWzBY4= 13 | github.com/dselans/dmidecode v0.0.0-20180814053009-65c3f9d81910/go.mod h1:yGxJ4za56u74+F00gmg9RJoyXLzvrOrIat4b/Dgw9Lo= 14 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 15 | github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= 16 | github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= 17 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 18 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 19 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 20 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 21 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 22 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 23 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 24 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 25 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 26 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 27 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 28 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 29 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 30 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 31 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 32 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 33 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 34 | github.com/shirou/gopsutil v2.19.10+incompatible h1:lA4Pi29JEVIQIgATSeftHSY0rMGI9CLrl2ZvDLiahto= 35 | github.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 36 | github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U= 37 | github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= 38 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 39 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 40 | github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= 41 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 42 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 43 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 44 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 45 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 46 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 47 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 48 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 49 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 50 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 51 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 52 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 53 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 54 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 55 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 56 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 57 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 58 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 59 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 60 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 61 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 62 | -------------------------------------------------------------------------------- /sal/sal.go: -------------------------------------------------------------------------------- 1 | // Package sal is a client for the Sal server API. 2 | package sal 3 | 4 | import ( 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "net" 10 | "net/http" 11 | "net/url" 12 | "os" 13 | "time" 14 | 15 | "github.com/airbnb/gosal/config" 16 | "github.com/airbnb/gosal/xpreports" 17 | "github.com/pkg/errors" 18 | ) 19 | 20 | // Client is what we need to send the POST request. 21 | type Client struct { 22 | User string 23 | Password string 24 | 25 | ServerURL *url.URL 26 | } 27 | 28 | // NewClient creates a new Sal API Client using Config. 29 | func NewClient(conf *config.Config) (*Client, error) { 30 | baseURL, err := url.Parse(conf.URL) 31 | if err != nil { 32 | return nil, fmt.Errorf("sal: parsing sal API URL: %s", err) 33 | } 34 | client := Client{ 35 | User: "sal", 36 | Password: conf.Key, 37 | ServerURL: baseURL, 38 | } 39 | 40 | return &client, nil 41 | } 42 | 43 | const checkinPath = "/checkin/" 44 | 45 | // Checkin is our POST request 46 | func (c *Client) Checkin(values *Data) error { 47 | checkinURL := c.ServerURL 48 | checkinURL.Path = checkinPath 49 | 50 | j, err := json.Marshal(values) 51 | if err != nil { 52 | fmt.Println(err) 53 | } 54 | // Create a new POST request with the urlencoded checkin values 55 | req, err := http.NewRequest("POST", checkinURL.String(), bytes.NewBuffer(j)) 56 | if err != nil { 57 | return fmt.Errorf("failed to create request: %s", err) 58 | } 59 | 60 | // The endpoint requires basic authentication, so set the username/password 61 | req.SetBasicAuth(c.User, c.Password) 62 | 63 | // We're sending URLEncoded data in the body, so tell the server what to expect 64 | req.Header.Set("Content-Type", "application/json") 65 | 66 | // Configure new http.client with timeouts 67 | httpclient := http.Client{ 68 | Timeout: 10 * time.Second, 69 | Transport: &http.Transport{ 70 | Dial: (&net.Dialer{ 71 | Timeout: 5 * time.Second, 72 | KeepAlive: 5 * time.Second, 73 | }).Dial, 74 | TLSHandshakeTimeout: 5 * time.Second, 75 | ResponseHeaderTimeout: 5 * time.Second, 76 | }, 77 | } 78 | 79 | // Execute the request 80 | resp, err := httpclient.Do(req) 81 | if err != nil { 82 | return fmt.Errorf("failed to checkin: %s", err) 83 | } 84 | 85 | defer resp.Body.Close() 86 | 87 | // Copy the body to the console in case there is any response 88 | io.Copy(os.Stdout, resp.Body) 89 | return nil 90 | } 91 | 92 | // SendCheckin uses Checkin to send our values to Sal 93 | func SendCheckin(conf *config.Config) error { 94 | client, err := NewClient(conf) 95 | if err != nil { 96 | return errors.Wrap(err, "creating gosal client from config") 97 | } 98 | 99 | // Execute a checkin, providing the data to send to the checkin endpoint 100 | report, err := xpreports.Build(conf) 101 | if err != nil { 102 | return errors.Wrap(err, "build report") 103 | } 104 | 105 | err = client.Checkin(&Data{ 106 | Machine: report.Machine, 107 | Sal: report.Sal, 108 | }) 109 | 110 | return errors.Wrap(err, "checkin") 111 | } 112 | 113 | // Data is the collective POST structure 114 | type Data struct { 115 | Machine *xpreports.Machine 116 | Sal *xpreports.Sal 117 | } 118 | -------------------------------------------------------------------------------- /sal/sal_test.go: -------------------------------------------------------------------------------- 1 | package sal 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/airbnb/gosal/config" 9 | "github.com/airbnb/gosal/xpreports" 10 | ) 11 | 12 | func TestCheckin(t *testing.T) { 13 | // test values 14 | serial := "serial" 15 | values := Data{ 16 | Machine: &xpreports.Machine{ 17 | Facts: &xpreports.MachineFacts{ 18 | CheckinModuleVersion: "version", 19 | }, 20 | ExtraData: &xpreports.MachineExtraData{ 21 | SerialNumber: serial, 22 | }, 23 | }, 24 | Sal: &xpreports.Sal{ 25 | Facts: &xpreports.SalFacts{ 26 | CheckinModuleVersion: "version", 27 | }, 28 | ExtraData: &xpreports.SalExtraData{ 29 | Key: "key", 30 | }, 31 | }, 32 | } 33 | 34 | // create a fake API endpoint served by the test server 35 | checkin := func(w http.ResponseWriter, r *http.Request) { 36 | if have, want := r.URL.Path, checkinPath; have != want { 37 | t.Errorf("have %s, want %s url path for checkin", have, want) 38 | } 39 | checkAuth(t, r) 40 | if have, want := values.Machine.ExtraData.SerialNumber, serial; have != want { 41 | t.Errorf("parsing serial from form: have %s, want %s", have, want) 42 | } 43 | } 44 | 45 | client, _, teardown := setupAPI(t, checkin) 46 | defer teardown() 47 | 48 | if err := client.Checkin(&values); err != nil { 49 | t.Fatal(err) 50 | } 51 | } 52 | 53 | const testPassword = "test" 54 | 55 | // check authentication sent by the sal client. 56 | func checkAuth(t *testing.T, r *http.Request) { 57 | username, password, ok := r.BasicAuth() 58 | if !ok { 59 | t.Errorf("could not parse BasicAuth from request") 60 | } 61 | 62 | if have, want := username, "sal"; have != want { 63 | t.Errorf("have %s, want %s", have, want) 64 | } 65 | 66 | if have, want := password, testPassword; have != want { 67 | t.Errorf("have %s, want %s", have, want) 68 | } 69 | } 70 | 71 | // setupAPI creates a sal client and a temporary server used for testing. 72 | func setupAPI(t *testing.T, h http.HandlerFunc) (client *Client, server *httptest.Server, cleanup func()) { 73 | server = httptest.NewServer(h) 74 | conf := &config.Config{ 75 | Key: testPassword, 76 | URL: server.URL, 77 | } 78 | 79 | client, err := NewClient(conf) 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | 84 | // add anything here that should be cleaned up when the tests are run. 85 | cleanup = func() { 86 | server.Close() 87 | } 88 | return client, server, cleanup 89 | } 90 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017 Kolide 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* 26 | Package version provides utilities for displaying version information about a Go application. 27 | 28 | To use this package, a program would set the package variables at build time, using the 29 | -ldflags go build flag. 30 | 31 | Example: 32 | go build -ldflags "-X github.com/kolide/kit/version.version=1.0.0" 33 | 34 | Available values and defaults to use with ldflags: 35 | 36 | version = "unknown" 37 | branch = "unknown" 38 | revision = "unknown" 39 | goVersion = "unknown" 40 | buildDate = "unknown" 41 | buildUser = "unknown" 42 | appName = "unknown" 43 | 44 | */ 45 | package version 46 | 47 | import ( 48 | "encoding/json" 49 | "fmt" 50 | "net/http" 51 | ) 52 | 53 | // These values are private which ensures they can only be set with the build flags. 54 | var ( 55 | version = "unknown" 56 | branch = "unknown" 57 | revision = "unknown" 58 | goVersion = "unknown" 59 | buildDate = "unknown" 60 | buildUser = "unknown" 61 | appName = "unknown" 62 | ) 63 | 64 | // Info is a structure with version build information about the current application. 65 | type Info struct { 66 | Version string `json:"version"` 67 | Branch string `json:"branch"` 68 | Revision string `json:"revision"` 69 | GoVersion string `json:"go_version"` 70 | BuildDate string `json:"build_date"` 71 | BuildUser string `json:"build_user"` 72 | } 73 | 74 | // Version returns a structure with the current version information. 75 | func Version() Info { 76 | return Info{ 77 | Version: version, 78 | Branch: branch, 79 | Revision: revision, 80 | GoVersion: goVersion, 81 | BuildDate: buildDate, 82 | BuildUser: buildUser, 83 | } 84 | } 85 | 86 | // Print outputs the application name and version string. 87 | func Print() { 88 | v := Version() 89 | fmt.Printf("%s version %s\n", appName, v.Version) 90 | } 91 | 92 | // PrintFull prints the application name and detailed version information. 93 | func PrintFull() { 94 | v := Version() 95 | fmt.Printf("%s - version %s\n", appName, v.Version) 96 | fmt.Printf(" branch: \t%s\n", v.Branch) 97 | fmt.Printf(" revision: \t%s\n", v.Revision) 98 | fmt.Printf(" build date: \t%s\n", v.BuildDate) 99 | fmt.Printf(" build user: \t%s\n", v.BuildUser) 100 | fmt.Printf(" go version: \t%s\n", v.GoVersion) 101 | } 102 | 103 | // Handler returns an HTTP Handler which returns JSON formatted version information. 104 | func Handler() http.Handler { 105 | v := Version() 106 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 107 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 108 | enc := json.NewEncoder(w) 109 | enc.SetIndent("", " ") 110 | enc.Encode(v) 111 | }) 112 | } 113 | -------------------------------------------------------------------------------- /version/version_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017 Kolide 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package version 25 | 26 | import ( 27 | "testing" 28 | "time" 29 | ) 30 | 31 | func TestVersion(t *testing.T) { 32 | now := time.Now().String() 33 | version = "test" 34 | buildDate = now 35 | 36 | info := Version() 37 | 38 | if have, want := info.Version, version; have != want { 39 | t.Errorf("have %s, want %s", have, want) 40 | } 41 | 42 | if have, want := info.BuildDate, now; have != want { 43 | t.Errorf("have %s, want %s", have, want) 44 | } 45 | 46 | if have, want := info.BuildUser, "unknown"; have != want { 47 | t.Errorf("have %s, want %s", have, want) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /xpreports/build_darwin.go: -------------------------------------------------------------------------------- 1 | package xpreports 2 | 3 | import "github.com/airbnb/gosal/config" 4 | 5 | // buildReport creates a report using macOS APIs and paths. 6 | func buildMachineReport(conf *config.Config) (*Machine, error) { 7 | panic("boom: I ran some macos code") 8 | } 9 | 10 | func buildSalReport(conf *config.Config) (*Sal, error) { 11 | panic("boom: sal report on darwin") 12 | } 13 | -------------------------------------------------------------------------------- /xpreports/build_linux.go: -------------------------------------------------------------------------------- 1 | package xpreports 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/airbnb/gosal/config" 7 | "github.com/airbnb/gosal/xpreports/linux" 8 | "github.com/pkg/errors" 9 | "github.com/shirou/gopsutil/host" 10 | "github.com/shirou/gopsutil/mem" 11 | ) 12 | 13 | // buildReport creates a report using linux APIs and paths. 14 | func buildMachineReport(conf *config.Config) (*Machine, error) { 15 | host, err := host.Info() 16 | if err != nil { 17 | return nil, errors.Wrap(err, "reports: getting host information") 18 | } 19 | 20 | disk, err := linux.Disk() 21 | if err != nil { 22 | return nil, errors.Wrap(err, "reports: getting root volume") 23 | } 24 | 25 | serial, err := linux.Serial() 26 | if err != nil { 27 | return nil, errors.Wrap(err, "reports: getting serial") 28 | } 29 | 30 | cpu, err := linux.GetProcessor() 31 | if err != nil { 32 | return nil, errors.Wrap(err, "machineinfo/gethardware: failed getting processor data") 33 | } 34 | 35 | consoleUser, err := linux.ConsoleUser() 36 | if err != nil { 37 | return nil, errors.Wrap(err, "reports: getting console user") 38 | } 39 | 40 | //Return the underling linux meminfo detail in bytes 41 | v, _ := mem.VirtualMemory() 42 | 43 | computerSystem, err := linux.GetComputerSystem() 44 | if err != nil { 45 | return nil, errors.Wrap(err, "reports: getting computerSystem") 46 | } 47 | 48 | // Convert memory from bytes to a human readable size format 49 | convertedMemory := float64(v.Total) 50 | var unitCount int 51 | var strMemory string 52 | 53 | for convertedMemory >= 1024 { 54 | convertedMemory = convertedMemory / 1024 55 | unitCount++ 56 | } 57 | 58 | switch unitCount { 59 | case 0: 60 | strMemory = strconv.FormatFloat(convertedMemory, 'f', 0, 64) + " B" 61 | case 1: 62 | strMemory = strconv.FormatFloat(convertedMemory, 'f', 0, 64) + " KB" 63 | case 2: 64 | strMemory = strconv.FormatFloat(convertedMemory, 'f', 0, 64) + " MB" 65 | case 3: 66 | strMemory = strconv.FormatFloat(convertedMemory, 'f', 0, 64) + " GB" 67 | case 4: 68 | strMemory = strconv.FormatFloat(convertedMemory, 'f', 0, 64) + " TB" 69 | } 70 | 71 | m := &Machine{ 72 | ExtraData: &MachineExtraData{ 73 | SerialNumber: serial, 74 | HostName: host.Hostname, 75 | ConsoleUser: consoleUser[0], 76 | OSFamily: "Linux", 77 | OperatingSystem: host.PlatformVersion, 78 | HDSpace: disk.FreeSpace, 79 | HDTotal: disk.Size, 80 | HDPercent: disk.PercentageFree, 81 | MachineModel: computerSystem.Model, 82 | MachineModelFriendly: "N/A", 83 | CPUType: cpu.CPUType, 84 | CPUSpeed: cpu.CurrentProcessorSpeed, 85 | Memory: strMemory, 86 | MemoryKB: int(v.Total) / 1024, 87 | }, Facts: &MachineFacts{ 88 | CheckinModuleVersion: "1", 89 | }, 90 | } 91 | 92 | return m, nil 93 | } 94 | 95 | func buildSalReport(conf *config.Config) (*Sal, error) { 96 | s := &Sal{ 97 | ExtraData: &SalExtraData{ 98 | Key: conf.Key, 99 | SalVersion: "1", 100 | }, Facts: &SalFacts{ 101 | CheckinModuleVersion: "1", 102 | }, 103 | } 104 | 105 | return s, nil 106 | } 107 | -------------------------------------------------------------------------------- /xpreports/build_windows.go: -------------------------------------------------------------------------------- 1 | package xpreports 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/airbnb/gosal/config" 7 | "github.com/airbnb/gosal/xpreports/windows" 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | // buildReport creates the necessary struct for Machine 12 | func buildMachineReport(conf *config.Config) (*Machine, error) { 13 | bios, err := windows.GetWin32Bios() 14 | if err != nil { 15 | return nil, errors.Wrap(err, "machineinfo/gethardware: failed getting bios data") 16 | } 17 | 18 | computerSystem, err := windows.GetWin32ComputerSystem() 19 | if err != nil { 20 | return nil, errors.Wrap(err, "machineinfo/gethardware: failed getting system data") 21 | } 22 | 23 | os, err := windows.GetWin32OS() 24 | if err != nil { 25 | return nil, errors.Wrap(err, "machineinfo/gethardware: failed getting os data") 26 | } 27 | 28 | cpu, err := windows.GetWin32Processor() 29 | if err != nil { 30 | return nil, errors.Wrap(err, "machineinfo/gethardware: failed getting processor data") 31 | } 32 | 33 | disk, err := windows.GetCDrive() 34 | if err != nil { 35 | return nil, errors.Wrap(err, "machineinfo/gethardware: failed getting information for c drive") 36 | } 37 | 38 | // Convert memory from kb to correct size 39 | convertedMemory := float64(os.TotalVisibleMemorySize) 40 | var unitCount int 41 | var strMemory string 42 | 43 | for convertedMemory >= 1024 { 44 | convertedMemory = convertedMemory / 1024 45 | unitCount++ 46 | } 47 | 48 | switch unitCount { 49 | case 0: 50 | strMemory = strconv.FormatFloat(convertedMemory, 'f', 0, 64) + " KB" 51 | case 1: 52 | strMemory = strconv.FormatFloat(convertedMemory, 'f', 0, 64) + " MB" 53 | case 2: 54 | strMemory = strconv.FormatFloat(convertedMemory, 'f', 0, 64) + " GB" 55 | case 3: 56 | strMemory = strconv.FormatFloat(convertedMemory, 'f', 0, 64) + " TB" 57 | } 58 | 59 | m := &Machine{ 60 | ExtraData: &MachineExtraData{ 61 | SerialNumber: bios.SerialNumber, 62 | HostName: bios.PSComputerName, 63 | ConsoleUser: computerSystem.UserName, 64 | OSFamily: "Windows", 65 | OperatingSystem: os.Caption + " " + os.Version, 66 | HDSpace: disk.FreeSpace, 67 | HDTotal: disk.Size, 68 | MachineModel: computerSystem.Model, 69 | MachineModelFriendly: "N/A", 70 | CPUType: cpu.CPUType, 71 | CPUSpeed: cpu.CPUSpeed, 72 | Memory: strMemory, 73 | MemoryKB: os.TotalVisibleMemorySize, 74 | }, Facts: &MachineFacts{ 75 | CheckinModuleVersion: "1", 76 | }, 77 | } 78 | 79 | return m, nil 80 | } 81 | 82 | func buildSalReport(conf *config.Config) (*Sal, error) { 83 | s := &Sal{ 84 | ExtraData: &SalExtraData{ 85 | Key: conf.Key, 86 | SalVersion: "1", 87 | }, Facts: &SalFacts{ 88 | CheckinModuleVersion: "1", 89 | }, 90 | } 91 | 92 | return s, nil 93 | } 94 | -------------------------------------------------------------------------------- /xpreports/cm/cm.go: -------------------------------------------------------------------------------- 1 | package cm 2 | 3 | import ( 4 | "encoding/json" 5 | "os/exec" 6 | "strings" 7 | 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | // Facts stores output of management tool 'facts' 12 | type Facts map[string]interface{} 13 | 14 | // GetFacts will exec into the Facts interface 15 | func GetFacts(confTool, cmdPath, cmdArgs string) (Facts, error) { 16 | args := strings.Split(cmdArgs, " ") 17 | cmd := exec.Command(cmdPath, args...) 18 | 19 | o, err := cmd.Output() 20 | if err != nil { 21 | return nil, errors.Wrap(err, "exec facts") 22 | } 23 | 24 | var facts Facts 25 | 26 | if err := json.Unmarshal(o, &facts); err != nil { 27 | return nil, errors.Wrap(err, "failed unmarshalling Facts") 28 | } 29 | 30 | switch confTool { 31 | case "puppet": 32 | facts = facts["values"].(map[string]interface{}) 33 | case "salt": 34 | facts = facts["local"].(map[string]interface{}) 35 | } 36 | 37 | facts = FlattenMapStringInterface(facts) 38 | 39 | return facts, nil 40 | } 41 | 42 | // FlattenMapStringInterface takes a Facts and returns a flattened Facts 43 | // with no null values 44 | func FlattenMapStringInterface(m Facts) Facts { 45 | o := make(Facts) 46 | for k, v := range m { 47 | switch child := v.(type) { 48 | case map[string]interface{}: 49 | nm := FlattenMapStringInterface(child) 50 | for nk, nv := range nm { 51 | o[k+"=>"+nk] = nv 52 | } 53 | default: 54 | // Delete null keys 55 | if v == nil { 56 | delete(m, k) 57 | break 58 | } 59 | o[k] = v 60 | } 61 | } 62 | return o 63 | } 64 | -------------------------------------------------------------------------------- /xpreports/cm/cm_test.go: -------------------------------------------------------------------------------- 1 | package cm 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func TestFlattenMapStringInterface(t *testing.T) { 9 | jsonTestWithNulls := `{ 10 | "name": "Jimmy", 11 | "age": 23, 12 | "test": null, 13 | "hello": { 14 | "world": null, 15 | "goodbye": "Jimmy" 16 | }, 17 | "alphabet": { 18 | "one": "alpha", 19 | "two": "beta", 20 | "three": "charlie", 21 | "so_nested": { 22 | "four": "delta", 23 | "five": "echo", 24 | "six": null 25 | } 26 | } 27 | }` 28 | 29 | var testFacts Facts 30 | 31 | if err := json.Unmarshal([]byte(jsonTestWithNulls), &testFacts); err != nil { 32 | t.Errorf("Unable to unmarshal 'jsonTestWithNulls'") 33 | } 34 | o := FlattenMapStringInterface(testFacts) 35 | 36 | null := nullSearcher(o) 37 | if null == true { 38 | t.Errorf("Null value found. Please fix 'FlattenMapStringInterface'") 39 | } 40 | } 41 | 42 | // nullSearcher recursively looks for null values. Returning true if any are found. 43 | func nullSearcher(aMap map[string]interface{}) bool { 44 | out := false 45 | for _, val := range aMap { 46 | if val == nil { 47 | return true 48 | } 49 | switch val.(type) { 50 | case map[string]interface{}: 51 | out = nullSearcher(val.(map[string]interface{})) 52 | } 53 | } 54 | return out 55 | } 56 | -------------------------------------------------------------------------------- /xpreports/linux/computersystem.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "github.com/dselans/dmidecode" 5 | ) 6 | 7 | // GetComputerSystem 8 | func GetComputerSystem() (LinuxComputerSystem, error) { 9 | dmi := dmidecode.New() 10 | 11 | _ = dmi.Run() 12 | 13 | byNameData, err := dmi.SearchByName("System Information") 14 | if err != nil { 15 | return LinuxComputerSystem{}, err 16 | } 17 | 18 | usernames, err := ConsoleUser() 19 | if err != nil { 20 | return LinuxComputerSystem{}, err 21 | } 22 | 23 | CompSys := LinuxComputerSystem{ 24 | UserName: usernames[0], 25 | Manufacturer: byNameData[0]["Manufacturer"], 26 | Model: byNameData[0]["Product Name"], 27 | } 28 | 29 | return CompSys, nil 30 | } 31 | 32 | // LinucComputerSystem 33 | type LinuxComputerSystem struct { 34 | UserName string 35 | Manufacturer string 36 | Model string 37 | } 38 | -------------------------------------------------------------------------------- /xpreports/linux/processor.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import "github.com/shirou/gopsutil/cpu" 4 | 5 | func GetProcessor() (Processor, error) { 6 | c, _ := cpu.Info() 7 | 8 | cpu := Processor{ 9 | CPUType: c[0].ModelName, 10 | CurrentProcessorSpeed: int(c[0].Mhz), 11 | } 12 | 13 | return cpu, nil 14 | } 15 | 16 | type Processor struct { 17 | CPUType string 18 | CurrentProcessorSpeed int 19 | } 20 | -------------------------------------------------------------------------------- /xpreports/linux/rootvolume.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import "github.com/shirou/gopsutil/disk" 4 | 5 | type LogicalDisk struct { 6 | Name string 7 | Size int 8 | FreeSpace int 9 | PercentageFree float32 10 | } 11 | 12 | func Disk() (LogicalDisk, error) { 13 | var d LogicalDisk 14 | 15 | rootdisk, err := disk.Usage("/") 16 | if err != nil { 17 | return LogicalDisk{}, err 18 | } 19 | 20 | d.Name = rootdisk.Path 21 | d.Size = int(rootdisk.Total) 22 | d.FreeSpace = int(rootdisk.Free) 23 | d.PercentageFree = float32(rootdisk.UsedPercent) 24 | 25 | return d, nil 26 | } 27 | -------------------------------------------------------------------------------- /xpreports/linux/serial.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "github.com/dselans/dmidecode" 5 | "github.com/pkg/errors" 6 | ) 7 | 8 | // Serial returns the system serial number 9 | func Serial() (string, error) { 10 | dmi := dmidecode.New() 11 | 12 | err := dmi.Run() 13 | if err != nil { 14 | return "", errors.Wrap(err, "DMI run") 15 | } 16 | 17 | byNameData, err := dmi.SearchByName("System Information") 18 | if err != nil { 19 | return "", errors.Wrap(err, "extracting information from DMI data") 20 | } 21 | 22 | return byNameData[0]["Serial Number"], nil 23 | } 24 | -------------------------------------------------------------------------------- /xpreports/linux/users.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "os/exec" 5 | "strings" 6 | ) 7 | 8 | func ConsoleUser() ([]string, error) { 9 | cmd := exec.Command("who", "-us") 10 | users, err := cmd.CombinedOutput() 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | cleaned := []string{} 16 | 17 | for _, user := range strings.Split(string(users), "\n") { 18 | clean := true 19 | col := strings.Split(user, " ") 20 | 21 | if len(col) > 0 { 22 | for _, cleanedU := range cleaned { 23 | u := strings.TrimSpace(col[0]) 24 | if len(u) == 0 || strings.Compare(cleanedU, col[0]) == 0 { 25 | clean = false 26 | } 27 | } 28 | if clean { 29 | cleaned = append(cleaned, col[0]) 30 | } 31 | } 32 | } 33 | 34 | return cleaned, nil 35 | } 36 | -------------------------------------------------------------------------------- /xpreports/reports.go: -------------------------------------------------------------------------------- 1 | package xpreports 2 | 3 | import ( 4 | "github.com/airbnb/gosal/config" 5 | ) 6 | 7 | // Report is a high level struct with individual checkin segments 8 | type Report struct { 9 | Machine *Machine 10 | Sal *Sal 11 | } 12 | 13 | // Machine blah 14 | type Machine struct { 15 | Facts *MachineFacts `json:"facts"` 16 | ExtraData *MachineExtraData `json:"extra_data"` 17 | } 18 | 19 | // Sal blah 20 | type Sal struct { 21 | ExtraData *SalExtraData `json:"extra_data"` 22 | Facts *SalFacts `json:"facts"` 23 | } 24 | 25 | // SalExtraData blah 26 | type SalExtraData struct { 27 | Key string `json:"key"` 28 | SalVersion string `json:"sal_version"` 29 | } 30 | 31 | // SalFacts blah 32 | type SalFacts struct { 33 | CheckinModuleVersion string `json:"checkin_module_version"` 34 | } 35 | 36 | // MachineFacts blah 37 | type MachineFacts struct { 38 | CheckinModuleVersion string `json:"checkin_module_version"` 39 | } 40 | 41 | // MachineExtraData blah 42 | type MachineExtraData struct { 43 | SerialNumber string `json:"serial"` 44 | HostName string `json:"hostname"` 45 | ConsoleUser string `json:"console_user"` 46 | OSFamily string `json:"os_family"` 47 | OperatingSystem string `json:"operating_system"` 48 | HDSpace int `json:"hd_space"` 49 | HDTotal int `json:"hd_total"` 50 | HDPercent float32 `json:"hd_percent"` 51 | MachineModel string `json:"machine_model"` 52 | MachineModelFriendly string `json:"machine_model_friendly"` 53 | CPUType string `json:"cpu_type"` 54 | CPUSpeed int `json:"cpu_speed"` 55 | Memory string `json:"memory"` 56 | MemoryKB int `json:"memory_kb"` 57 | } 58 | 59 | // Build creates a report for the sal server. 60 | // Build supports darwin, windows and linux and will use 61 | // the appropriate APIs for each system. 62 | func Build(conf *config.Config) (*Report, error) { 63 | // buildReport is implented separately for each 64 | // operating system. 65 | machineReport, err := buildMachineReport(conf) 66 | if err != nil { 67 | return nil, err 68 | } 69 | salReport, err := buildSalReport(conf) 70 | if err != nil { 71 | return nil, err 72 | } 73 | report := &Report{ 74 | Machine: machineReport, 75 | Sal: salReport, 76 | } 77 | 78 | return report, err 79 | } 80 | -------------------------------------------------------------------------------- /xpreports/windows/win32_bios.go: -------------------------------------------------------------------------------- 1 | package windows 2 | 3 | import ( 4 | "encoding/json" 5 | "os/exec" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | // GetWin32Bios exports the win32_bios powershell class 11 | func GetWin32Bios() (Win32Bios, error) { 12 | cmd := exec.Command("powershell", "gwmi", "Win32_Bios", "|", "ConvertTo-Json") 13 | 14 | // cmd.Stderr = os.Stderr 15 | o, err := cmd.Output() 16 | if err != nil { 17 | return Win32Bios{}, errors.Wrap(err, "exec gwmi Win32_Bios") 18 | } 19 | 20 | var j Win32Bios 21 | 22 | if err := json.Unmarshal(o, &j); err != nil { 23 | return Win32Bios{}, errors.Wrap(err, "failed unmarshalling Win32_Bios") 24 | } 25 | 26 | return j, nil 27 | } 28 | 29 | // Win32Bios data structure 30 | type Win32Bios struct { 31 | PSComputerName string `json:"PSComputerName"` 32 | SerialNumber string `json:"SerialNumber"` 33 | } 34 | -------------------------------------------------------------------------------- /xpreports/windows/win32_computersystem.go: -------------------------------------------------------------------------------- 1 | package windows 2 | 3 | import ( 4 | "encoding/json" 5 | "os/exec" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | // GetWin32ComputerSystem exports win32_ComputerSystem powershell class 11 | func GetWin32ComputerSystem() (Win32ComputerSystem, error) { 12 | cmd := exec.Command("powershell", "gwmi", "Win32_ComputerSystem", "|", "ConvertTo-Json") 13 | 14 | // cmd.Stderr = os.Stderr 15 | o, err := cmd.Output() 16 | if err != nil { 17 | return Win32ComputerSystem{}, errors.Wrap(err, "exec gwmi Win32_ComputerSystem") 18 | } 19 | 20 | var j Win32ComputerSystem 21 | 22 | if err := json.Unmarshal(o, &j); err != nil { 23 | return Win32ComputerSystem{}, errors.Wrap(err, "failed unmarshalling Win32_ComputerSystem") 24 | } 25 | 26 | return j, nil 27 | } 28 | 29 | // Win32ComputerSystem structure 30 | type Win32ComputerSystem struct { 31 | UserName string `json:"UserName"` 32 | Manufacturer string `json:"Manufacturer"` 33 | Model string `json:"Model"` 34 | } 35 | -------------------------------------------------------------------------------- /xpreports/windows/win32_logicaldisk.go: -------------------------------------------------------------------------------- 1 | package windows 2 | 3 | import ( 4 | "encoding/json" 5 | "os/exec" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | // GetWin32LogicalDisk returns an array of powershell class win32_logicaldisk 11 | func GetWin32LogicalDisk() ([]Win32LogicalDisk, error) { 12 | cmd := exec.Command("powershell", "gwmi", "Win32_LogicalDisk", "|", "ConvertTo-Json") 13 | 14 | // cmd.Stderr = os.Stderr 15 | o, err := cmd.Output() 16 | if err != nil { 17 | return nil, errors.Wrap(err, "exec gwmi Win32_LogicalDisk") 18 | } 19 | 20 | var j []Win32LogicalDisk 21 | 22 | if err := json.Unmarshal(o, &j); err != nil { 23 | return nil, errors.Wrap(err, "failed unmarshalling Win32LogicalDisk") 24 | } 25 | 26 | return j, nil 27 | } 28 | 29 | // Win32LogicalDisk structure 30 | type Win32LogicalDisk struct { 31 | Name string `json:"Name"` 32 | Size int `json:"Size"` 33 | FreeSpace int `json:"FreeSpace"` 34 | } 35 | 36 | // GetCDrive explicity looks for C Drive 37 | func GetCDrive() (Win32LogicalDisk, error) { 38 | disks, _ := GetWin32LogicalDisk() 39 | 40 | var c Win32LogicalDisk 41 | 42 | for _, element := range disks { 43 | if element.Name == "C:" { 44 | c.Name = element.Name 45 | c.Size = (element.Size) 46 | c.FreeSpace = (element.FreeSpace) 47 | } 48 | } 49 | 50 | return c, nil 51 | } 52 | -------------------------------------------------------------------------------- /xpreports/windows/win32_os.go: -------------------------------------------------------------------------------- 1 | package windows 2 | 3 | import ( 4 | "encoding/json" 5 | "os/exec" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | // GetWin32OS exports win32_operatingsystem powershell class 11 | func GetWin32OS() (Win32OS, error) { 12 | cmd := exec.Command("powershell", "gwmi", "Win32_OperatingSystem", "|", "ConvertTo-Json") 13 | 14 | // cmd.Stderr = os.Stderr 15 | o, err := cmd.Output() 16 | if err != nil { 17 | return Win32OS{}, errors.Wrap(err, "exec gwmi Win32_OperatingSystem") 18 | } 19 | 20 | var j Win32OS 21 | 22 | if err := json.Unmarshal(o, &j); err != nil { 23 | return Win32OS{}, errors.Wrap(err, "failed unmarshalling Win32_OperatingSystem") 24 | } 25 | 26 | return j, nil 27 | } 28 | 29 | // Win32OS structure 30 | type Win32OS struct { 31 | Caption string `json:"Caption"` // os version 32 | Version string `json:"Version"` 33 | TotalVirtualMemorySize int `json:"TotalVirtualMemorySize"` 34 | TotalVisibleMemorySize int `json:"TotalVisibleMemorySize"` 35 | } 36 | -------------------------------------------------------------------------------- /xpreports/windows/win32_processor.go: -------------------------------------------------------------------------------- 1 | package windows 2 | 3 | import ( 4 | "encoding/json" 5 | "os/exec" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | // GetWin32Processor exports the win32_bios powershell class 11 | func GetWin32Processor() (Win32Processor, error) { 12 | cmd := exec.Command("powershell", "gwmi", "Win32_Processor", "|", "ConvertTo-Json") 13 | 14 | // cmd.Stderr = os.Stderr 15 | o, err := cmd.Output() 16 | if err != nil { 17 | return Win32Processor{}, errors.Wrap(err, "gwmi exec Win32_Processor") 18 | } 19 | 20 | var j Win32Processor 21 | 22 | if err := json.Unmarshal(o, &j); err != nil { 23 | return Win32Processor{}, errors.Wrap(err, "failed unmarshalling Win32_Processor") 24 | } 25 | 26 | return j, nil 27 | } 28 | 29 | // Win32Processor data structure 30 | type Win32Processor struct { 31 | CPUType string `json:"Name"` 32 | CPUSpeed int `json:"MaxClockSpeed"` 33 | } 34 | --------------------------------------------------------------------------------