Permalink
Browse files

add puppet integration code

Puppet can be used on the basis of the ffrank-mgmtgraph module.
There are three modes available:
* fetching catalogs from the master (--puppet agent)
* compiling a manifest from a local file (--puppet /path/to/file.pp)
* compiling a manifest from the cli (--puppet "<manifest>")

Catalogs from the master are currently never refreshed. We should
add some more code to re-run the parsing function at an interval
equal to Puppet's local 'runinterval' setting.

There is also still a distinct lack of tests.

Still, this fixes #8
  • Loading branch information...
1 parent 2eed4bd commit 8f83ecee65e070da53fc884e5a7ddbf93b7af1f6 @ffrank ffrank committed May 24, 2016
Showing with 166 additions and 3 deletions.
  1. +29 −3 main.go
  2. +137 −0 puppet.go
View
@@ -65,6 +65,11 @@ func run(c *cli.Context) error {
log.Printf("Main: Start: %v", start)
var G, fullGraph *Graph
+ if c.IsSet("file") && c.IsSet("puppet") {
+ log.Println("the --file and --puppet parameters cannot be used together")
+ return cli.NewExitError("", 1)
+ }
+
// exit after `max-runtime` seconds for no reason at all...
if i := c.Int("max-runtime"); i > 0 {
go func() {
@@ -110,9 +115,13 @@ func run(c *cli.Context) error {
startchan := make(chan struct{}) // start signal
go func() { startchan <- struct{}{} }()
file := c.String("file")
- configchan := make(chan bool)
- if !c.Bool("no-watch") {
+ var configchan chan bool
+ var puppetchan <-chan time.Time
+ if !c.Bool("no-watch") && c.IsSet("file") {
configchan = ConfigWatch(file)
+ } else if c.IsSet("puppet") {
+ interval := PuppetInterval(c.String("puppet-conf"))
+ puppetchan = time.Tick(time.Duration(interval) * time.Second)
}
log.Println("Etcd: Starting...")
etcdchan := etcdO.EtcdWatch()
@@ -133,6 +142,8 @@ func run(c *cli.Context) error {
default:
log.Fatal("Etcd: Unhandled message: ", msg)
}
+ case _ = <-puppetchan:
+ // nothing, just go on
case msg := <-configchan:
if c.Bool("no-watch") || !msg {
continue // not ready to read config
@@ -144,7 +155,12 @@ func run(c *cli.Context) error {
return
}
- config := ParseConfigFromFile(file)
+ var config *GraphConfig
+ if c.IsSet("file") {
+ config = ParseConfigFromFile(file)
+ } else if c.IsSet("puppet") {
+ config = ParseConfigFromPuppet(c.String("puppet"), c.String("puppet-conf"))
+ }
if config == nil {
log.Printf("Config parse failure")
continue
@@ -300,6 +316,16 @@ func main() {
Name: "noop",
Usage: "globally force all resources into no-op mode",
},
+ cli.StringFlag{
+ Name: "puppet, p",
+ Value: "",
+ Usage: "load graph from puppet, optionally takes a manifest or path to manifest file",
+ },
+ cli.StringFlag{
+ Name: "puppet-conf",
+ Value: "",
+ Usage: "supply the path to an alternate puppet.conf file to use",
+ },
},
},
}
View
@@ -0,0 +1,137 @@
+// Mgmt
+// Copyright (C) 2013-2016+ James Shubin and the project contributors
+// Written by James Shubin <james@shubin.ca> and the project contributors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package main
+
+import (
+ "bufio"
+ "io"
+ "log"
+ "os/exec"
+ "strconv"
+ "strings"
+)
+
+const (
+ PuppetYAMLBufferSize = 65535
+)
+
+func runPuppetCommand(cmd *exec.Cmd) ([]byte, error) {
+ if DEBUG {
+ log.Printf("Puppet: running command: %v", cmd)
+ }
+
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ log.Printf("Puppet: Error opening pipe to puppet command: %v", err)
+ return nil, err
+ }
+ stderr, err := cmd.StderrPipe()
+ if err != nil {
+ log.Printf("Puppet: Error opening error pipe to puppet command: %v", err)
+ return nil, err
+ }
+
+ if err := cmd.Start(); err != nil {
+ log.Printf("Puppet: Error starting puppet command: %v", err)
+ return nil, err
+ }
+
+ // XXX: the current implementation is likely prone to fail
+ // as soon as the YAML data overflows the buffer.
+ data := make([]byte, PuppetYAMLBufferSize)
+ var result []byte
+ for err == nil {
+ var count int
+ count, err = stdout.Read(data)
+ if err != nil && err != io.EOF {
+ log.Printf("Puppet: Error reading YAML data from puppet: %v", err)
+ return nil, err
+ }
+ // Slicing down to the number of actual bytes is important, the YAML parser
+ // will choke on an oversized slice. http://stackoverflow.com/a/33726617/3356612
+ result = append(result, data[0:count]...)
+ }
+ if DEBUG {
+ log.Printf("Puppet: read %v bytes of data from puppet", len(result))
+ }
+ for scanner := bufio.NewScanner(stderr); scanner.Scan(); {
+ log.Printf("Puppet: (output) %v", scanner.Text())
+ }
+ if err := cmd.Wait(); err != nil {
+ log.Printf("Puppet: Error: puppet command did not complete: %v", err)
+ return nil, err
+ }
+
+ return result, nil
+}
+
+func ParseConfigFromPuppet(puppetParam, puppetConf string) *GraphConfig {
+ var puppetConfArg string
+ if puppetConf != "" {
+ puppetConfArg = "--config=" + puppetConf
+ }
+
+ var cmd *exec.Cmd
+ if puppetParam == "agent" {
+ cmd = exec.Command("puppet", "mgmtgraph", "print", puppetConfArg)
+ } else if strings.HasSuffix(puppetParam, ".pp") {
+ cmd = exec.Command("puppet", "mgmtgraph", "print", puppetConfArg, "--manifest", puppetParam)
+ } else {
+ cmd = exec.Command("puppet", "mgmtgraph", "print", puppetConfArg, "--code", puppetParam)
+ }
+
+ log.Println("Puppet: launching translator")
+
+ var config GraphConfig
+ if data, err := runPuppetCommand(cmd); err != nil {
+ return nil
+ } else if err := config.Parse(data); err != nil {
+ log.Printf("Puppet: Error: Could not parse YAML output with Parse: %v", err)
+ return nil
+ }
+
+ return &config
+}
+
+func PuppetInterval(puppetConf string) int {
+ if DEBUG {
+ log.Printf("Puppet: determining graph refresh interval")
+ }
+ var cmd *exec.Cmd
+ if puppetConf != "" {
+ cmd = exec.Command("puppet", "config", "print", "runinterval", "--config", puppetConf)
+ } else {
+ cmd = exec.Command("puppet", "config", "print", "runinterval")
+ }
+
+ log.Println("Puppet: inspecting runinterval configuration")
+
+ interval := 1800
+ data, err := runPuppetCommand(cmd)
+ if err != nil {
+ log.Printf("Puppet: could not determine configured run interval (%v), using default of %v", err, interval)
+ return interval
+ }
+ result, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 0)
+ if err != nil {
+ log.Printf("Puppet: error reading numeric runinterval value (%v), using default of %v", err, interval)
+ return interval
+ }
+
+ return int(result)
+}

0 comments on commit 8f83ece

Please sign in to comment.