-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 9d6bec2
Showing
12 changed files
with
371 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
tests/secrets |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Copyright (c) 2022 Dyntek Services Inc. | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining | ||
a copy of this software and associated documentation files (the | ||
"Software"), to deal in the Software without restriction, including | ||
without limitation the rights to use, copy, modify, merge, publish, | ||
distribute, sublicense, and/or sell copies of the Software, and to | ||
permit persons to whom the Software is furnished to do so, subject to | ||
the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be | ||
included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# <img src="favicon.png" height="64"/> GoConfigure | ||
|
||
> A simple SSH configuration deployment tool for the command-line. | ||
# Operation | ||
|
||
GoConfigure can be run at the command-line with `goconfigure`. Running `goconfigure` without | ||
any arguments will launch an interactive session where target devices and commands can be | ||
entered manually. Interactive mode does **not** support configuration templating. | ||
|
||
Optional arguments include `-i inventory_filename` and `-t template_filename`. The template | ||
should be defined in a plain-text document and the inventory filename should be defined in a | ||
YAML formatted document according to the following schema; | ||
```yaml | ||
--- | ||
server-1: | ||
hostname: s1.yourdomain.com | ||
username: username | ||
password: password | ||
data: | ||
ip_address: 192.168.0.1 | ||
server-2: | ||
hostname: s2.yourdomain.com | ||
username: username | ||
password: password | ||
``` | ||
*Note that `data` is an optional field*. The fields defined in `data` will be available to the | ||
template during rendering. Fields required by the template must be defined in `data`. Field names | ||
should be uppercase to be accessible from within the template. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
module github.com/dyntek-services-inc/goconfigure | ||
|
||
go 1.18 | ||
|
||
require ( | ||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect | ||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= | ||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= | ||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package inventory | ||
|
||
import ( | ||
"gopkg.in/yaml.v3" | ||
"io" | ||
"os" | ||
) | ||
|
||
type Inventory map[string]Host | ||
|
||
type Host struct { | ||
Hostname string `yaml:"hostname"` | ||
Username string `yaml:"username"` | ||
Password string `yaml:"password"` | ||
Data map[string]interface{} `yaml:"data"` | ||
} | ||
|
||
func LoadInventory(filename string) (Inventory, error) { | ||
inv := Inventory{} | ||
yFile, err := os.Open(filename) | ||
if err != nil { | ||
return nil, err | ||
} | ||
yContent, err := io.ReadAll(yFile) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err := yaml.Unmarshal(yContent, &inv); err != nil { | ||
return nil, err | ||
} | ||
return inv, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// GoConfigure is an application for templating configurations meant to be pushed via SSH. | ||
// It can be consumed as a command line tool. Passing arguments of the form `goconfigure | ||
// -t TEMPLATE_FILE_NAME -i DATA_FILE_NAME` will render and push the render to the devices | ||
// defined in the inventory file. | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"github.com/dyntek-services-inc/goconfigure/inventory" | ||
"github.com/dyntek-services-inc/goconfigure/render" | ||
"github.com/dyntek-services-inc/goconfigure/ssh" | ||
"log" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
var pwd, pwdErr = os.Getwd() | ||
|
||
func deploy(inventory inventory.Inventory, tplString string) error { | ||
rc := make(map[string]chan []string, len(inventory)) // The response channels. | ||
for name, host := range inventory { | ||
log.Printf("starting deployment for %s", host.Hostname) | ||
rc[name] = make(chan []string) | ||
handler, err := ssh.Connect(host) | ||
log.Printf("finished connecting too %s", host.Hostname) | ||
if err != nil { | ||
return err | ||
} | ||
go func(ro chan []string, hdlr *ssh.Handler) { | ||
rtplc := render.RenderCommands(hdlr.GetHost().Data, tplString) | ||
cc := make([]chan string, len(rtplc)) | ||
for i, c := range rtplc { | ||
cc[i] = make(chan string) | ||
go func(co chan string, ci string) { | ||
r, err := hdlr.Send(ci) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
co <- r | ||
}(cc[i], c) | ||
} | ||
cco := make([]string, len(rtplc)) | ||
for i, co := range cc { | ||
cco[i] = <-co | ||
} | ||
log.Printf("finished deployment of %s", hdlr.GetHost().Hostname) | ||
ro <- cco | ||
}(rc[name], handler) | ||
} | ||
for name, ro := range rc { | ||
if pwdErr != nil { | ||
return pwdErr | ||
} | ||
rro := <-ro | ||
tr := strings.Join(rro, "\n") | ||
of := filepath.Join(pwd, name) | ||
log.Printf("writing output to %s.txt", of) | ||
if err := os.WriteFile(of+".txt", []byte(tr), 0666); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func main() { | ||
invFilename := flag.String("i", "", "inventory filename") | ||
tplFilename := flag.String("t", "", "template filename") | ||
flag.Parse() | ||
if len(*invFilename) == 0 && len(*tplFilename) == 0 { | ||
// No inventory or template flags were passed, start manual mode | ||
// TODO: implement manual mode | ||
} else { | ||
if len(*invFilename) == 0 || len(*tplFilename) == 0 { | ||
// One of the flags was passed but not the other | ||
log.Fatal("one flag was passed, but not both") | ||
} else { | ||
// Both flags were passed, begin deployment | ||
inv, err := inventory.LoadInventory(*invFilename) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
tplString, err := render.FileToString(*tplFilename) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
if err := deploy(inv, tplString); err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package render | ||
|
||
import ( | ||
"bytes" | ||
"os" | ||
"strings" | ||
"text/template" | ||
) | ||
|
||
// FileToString will accept a render file and return the render as a string. | ||
func FileToString(tplFilename string) (string, error) { | ||
readBytes, err := os.ReadFile(tplFilename) | ||
if err != nil { | ||
return "", err | ||
} | ||
return string(readBytes), nil | ||
} | ||
|
||
func RenderCommands(data map[string]interface{}, tplString string) []string { | ||
tpl := template.Must(template.New("").Parse(tplString)) | ||
var tplBuffer bytes.Buffer | ||
tpl.Execute(&tplBuffer, data) | ||
return strings.Split(tplBuffer.String(), "\n") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package ssh | ||
|
||
import ( | ||
"bytes" | ||
"github.com/dyntek-services-inc/goconfigure/inventory" | ||
"golang.org/x/crypto/ssh" | ||
) | ||
|
||
type Handler struct { | ||
host inventory.Host | ||
client *ssh.Client | ||
} | ||
|
||
func Connect(host inventory.Host) (*Handler, error) { | ||
config := &ssh.ClientConfig{ | ||
User: host.Username, | ||
Auth: []ssh.AuthMethod{ | ||
ssh.Password(host.Password), | ||
}, | ||
HostKeyCallback: ssh.InsecureIgnoreHostKey(), | ||
} | ||
client, err := ssh.Dial("tcp", host.Hostname+":22", config) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &Handler{client: client, host: host}, nil | ||
} | ||
|
||
func (h Handler) GetHost() inventory.Host { | ||
return h.host | ||
} | ||
|
||
// Send opens a new session to the SSH server and sends the passed string. | ||
// The standard output from the server is returned. | ||
func (h Handler) Send(command string) (string, error) { | ||
session, err := h.client.NewSession() | ||
if err != nil { | ||
return "", err | ||
} | ||
defer session.Close() | ||
var outBuffer bytes.Buffer | ||
session.Stdout = &outBuffer | ||
session.Run(command) | ||
return outBuffer.String(), nil | ||
} | ||
|
||
func (h Handler) Close() error { | ||
return h.client.Close() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package tests | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"github.com/dyntek-services-inc/goconfigure/inventory" | ||
"github.com/dyntek-services-inc/goconfigure/render" | ||
"github.com/dyntek-services-inc/goconfigure/ssh" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestConnectHandler(t *testing.T) { | ||
inv, err := inventory.LoadInventory("secrets/hosts.yml") | ||
if err != nil { | ||
panic(err) | ||
} | ||
// Connect to Hosts | ||
var handlers []*ssh.Handler | ||
for name, host := range inv { | ||
t.Logf("Connectiong to %s", name) | ||
h, err := ssh.Connect(host) | ||
handlers = append(handlers, h) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
// Cleanup | ||
for _, h := range handlers { | ||
h.Close() | ||
} | ||
} | ||
|
||
func TestSendHandler(t *testing.T) { | ||
inv, err := inventory.LoadInventory("secrets/hosts.yml") | ||
if err != nil { | ||
panic(err) | ||
} | ||
// Connect to Hosts | ||
var handlers []*ssh.Handler | ||
for name, host := range inv { | ||
t.Logf("Connectiong to %s", name) | ||
h, err := ssh.Connect(host) | ||
handlers = append(handlers, h) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
// Send Command to Host | ||
for _, h := range handlers { | ||
response, err := h.Send("echo \"hello world!\"") | ||
if err != nil { | ||
panic(err) | ||
} | ||
response = strings.TrimSpace(response) | ||
if response != "hello world!" { | ||
panic(errors.New(fmt.Sprintf("response %s not equal to %s", response, "hello world!"))) | ||
} else { | ||
t.Logf("response %s succesfully matches %s", response, "hello world!") | ||
} | ||
} | ||
// Cleanup | ||
for _, h := range handlers { | ||
h.Close() | ||
} | ||
} | ||
|
||
func TestMultiSendHandler(t *testing.T) { | ||
inv, err := inventory.LoadInventory("secrets/hosts.yml") | ||
tplString, err := render.FileToString("secrets/example.txt") | ||
if err != nil { | ||
panic(err) | ||
} | ||
// Connect to Hosts and Render Template | ||
var handlers []*ssh.Handler | ||
var tpls [][]string | ||
for name, host := range inv { | ||
t.Logf("Connectiong to %s", name) | ||
h, err := ssh.Connect(host) | ||
if err != nil { | ||
panic(err) | ||
} | ||
handlers = append(handlers, h) | ||
tpls = append(tpls, render.RenderCommands(host.Data, tplString)) | ||
} | ||
// Send Commands to Host | ||
for i, h := range handlers { | ||
for _, command := range tpls[i] { | ||
response, err := h.Send(command) | ||
if err != nil { | ||
panic(err) | ||
} | ||
response = strings.TrimSpace(response) | ||
fmt.Println(response) | ||
} | ||
//if response != "hello world!" { | ||
// panic(errors.New(fmt.Sprintf("response %s not equal to %s", response, "hello world!"))) | ||
//} else { | ||
// t.Logf("response %s succesfully matches %s", response, "hello world!") | ||
//} | ||
} | ||
// Cleanup | ||
for _, h := range handlers { | ||
h.Close() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package tests |