<file_sep>all: build

APP_NAME = puppet
PKGDIR_TMP = ${TMPDIR}golang

.pre-build:
	mkdir -p build

deps:
	go get -u github.com/golang/dep/...
	go get -u golang.org/x/lint/golint
	dep ensure -vendor-only -v

clean:
	rm -rf build/
	rm -rf ${PKGDIR_TMP}_darwin

build: .pre-build
	GOOS=darwin go build -i -o build/${APP_NAME}.darwin.ext -pkgdir ${PKGDIR_TMP}
	GOOS=windows go build -i -o build/${APP_NAME}.windows.ext.exe -pkgdir ${PKGDIR_TMP}


lastrun:
	rm last_run_report.yaml
	sudo cp /opt/puppetlabs/puppet/cache/state/last_run_report.yaml last_run_report.yaml
	sudo chown graham_gilbert:admin last_run_report.yaml<file_sep>package main

import (
	"flag"
	"log"
	"time"

	osquery "github.com/kolide/osquery-go"
	"github.com/kolide/osquery-go/plugin/table"
)

func main() {
	var (
		flSocketPath = flag.String("socket", "", "")
		flTimeout    = flag.Int("timeout", 0, "")
		_            = flag.Int("interval", 0, "")
		_            = flag.Bool("verbose", false, "")
	)
	flag.Parse()

	// allow for osqueryd to create the socket path otherwise it will error
	time.Sleep(2 * time.Second)

	server, err := osquery.NewExtensionManagerServer(
		"puppet_state",
		*flSocketPath,
		osquery.ServerTimeout(time.Duration(*flTimeout)*time.Second),
	)
	if err != nil {
		log.Fatalf("Error creating extension: %s\n", err)
	}

	// Create and register a new table plugin with the server.
	// Adding a new table? Add it to the list and the loop below will handle
	// the registration for you.
	plugins := []osquery.OsqueryPlugin{
		table.NewPlugin("puppet_info", PuppetInfoColumns(), PuppetInfoGenerate),
		table.NewPlugin("puppet_logs", PuppetLogsColumns(), PuppetLogsGenerate),
		table.NewPlugin("puppet_state", PuppetStateColumns(), PuppetStateGenerate),
	}

	for _, p := range plugins {
		server.RegisterPlugin(p)
	}

	// Start the server. It will run forever unless an error bubbles up.
	if err := server.Run(); err != nil {
		log.Fatalln(err)
	}
}
<file_sep>package main

import (
	"context"

	"github.com/kolide/osquery-go/plugin/table"
)

type Log struct {
	Level   string   `yaml:"level"`
	Message string   `yaml:"message"`
	Source  string   `yaml:"source"`
	Tags    []string `yaml:"tags"`
	Time    string   `yaml:"time"`
	File    string   `yaml:"file"`
	Line    string   `yaml:"line"`
}

// Columns returns the type hinted columns for the logged in user.
func PuppetLogsColumns() []table.ColumnDefinition {
	return []table.ColumnDefinition{
		table.TextColumn("level"),
		table.TextColumn("message"),
		table.TextColumn("source"),
		// table.TextColumn("tags"),
		table.TextColumn("time"),
		table.TextColumn("file"),
		table.TextColumn("line"),
	}
}

// Generate will be called whenever the table is queried. Since our data in these
// plugins is flat it will return a single row.
func PuppetLogsGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
	var results []map[string]string
	runData, err := GetPuppetYaml()
	if err != nil {
		return results, err
	}

	for _, item := range runData.Logs {
		results = append(results, map[string]string{
			"level":   item.Level,
			"message": item.Message,
			"source":  item.Source,
			// "tags":    parseList(item.Tags),
			"time": item.Time,
			"file": item.File,
			"line": item.Line,
		})
	}

	return results, nil
}
<file_sep>package main

import (
	"bytes"
	"os"
	"runtime"
	"strings"

	"gopkg.in/yaml.v3"
)

func YamlPath() string {
	if runtime.GOOS == "windows" {
		return "C:\\ProgramData\\PuppetLabs\\puppet\\cache\\state\\last_run_report.yaml"
	}

	return "/opt/puppetlabs/puppet/cache/state/last_run_report.yaml"
	// This is for when testing with a local report file
	// filename, _ := filepath.Abs("./last_run_report.yaml")
	// fmt.Print(filename)
	// return filename
}

func GetPuppetYaml() (*PuppetInfo, error) {

	var yamlData PuppetInfo
	yamlFile, err := os.Open(YamlPath())

	if err != nil {
		// fmt.Print(err)
		return &yamlData, err
	}

	buf := new(bytes.Buffer)
	buf.ReadFrom(yamlFile)
	yamlString := buf.String()
	yamlString = strings.Replace(yamlString, "\r", "\n", -1)

	err = yaml.Unmarshal([]byte(yamlString), &yamlData)
	if err != nil {
		// fmt.Print("Error during unmarshal")
		// fmt.Print(err)
		return &yamlData, err
	}

	return &yamlData, nil
}
<file_sep>package main

import (
	"context"

	"github.com/kolide/osquery-go/plugin/table"
)

// PuppetInfo
type PuppetInfo struct {
	CachedCatalogStatus  string `yaml:"cached_catalog_status"`
	CatalogUUID          string `yaml:"catalog_uuid"`
	CodeID               string `yaml:"code_id"`
	ConfigurationVersion string `yaml:"configuration_version"`
	CorrectiveChange     string `yaml:"corrective_change"`
	Environment          string `yaml:"environment"`
	Host                 string `yaml:"host"`
	Kind                 string `yaml:"kind"`
	MasterUsed           string `yaml:"master_used"`
	Noop                 string `yaml:"noop"`
	NoopPending          string `yaml:"noop_pending"`
	PuppetVersion        string `yaml:"puppet_version"`
	ReportFormat         string `yaml:"report_format"`
	Status               string `yaml:"status"`
	Time                 string `yaml:"time"`
	TransactionCompleted string `yaml:"transaction_completed"`
	TransactionUUID      string `yaml:"transaction_uuid"`
	Logs                 []Log
	ResourceStatuses     map[string]ResourceStatus `yaml:"resource_statuses"`
	// Metrics          interface{}               `yaml:"metrics"`
}

func PuppetInfoColumns() []table.ColumnDefinition {
	return []table.ColumnDefinition{
		table.TextColumn("cached_catalog_status"),
		table.TextColumn("catalog_uuid"),
		table.TextColumn("code_id"),
		table.TextColumn("configuration_version"),
		table.TextColumn("corrective_change"),
		table.TextColumn("environment"),
		table.TextColumn("host"),
		table.TextColumn("kind"),
		table.TextColumn("master_used"),
		table.TextColumn("noop"),
		table.TextColumn("noop_pending"),
		table.TextColumn("puppet_version"),
		table.TextColumn("report_format"),
		table.TextColumn("status"),
		table.TextColumn("time"),
		table.TextColumn("transaction_completed"),
		table.TextColumn("transaction_uuid"),
	}
}

// Generate will be called whenever the table is queried. Since our data in these
// plugins is flat it will return a single row.
func PuppetInfoGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
	var results []map[string]string
	runData, err := GetPuppetYaml()
	if err != nil {
		// fmt.Print(runData)
		// fmt.Println(err)
		return results, err
	}

	// fmt.Println(runData)
	results = append(results, map[string]string{
		"cached_catalog_status": runData.CachedCatalogStatus,
		"catalog_uuid":          runData.CatalogUUID,
		"code_id":               runData.CodeID,
		"configuration_version": runData.ConfigurationVersion,
		"corrective_change":     runData.CorrectiveChange,
		"environment":           runData.Environment,
		"host":                  runData.Host,
		"kind":                  runData.Kind,
		"master_used":           runData.MasterUsed,
		"noop":                  runData.Noop,
		"noop_pending":          runData.NoopPending,
		"puppet_version":        runData.PuppetVersion,
		"report_format":         runData.ReportFormat,
		"status":                runData.Status,
		"time":                  runData.Time,
		"transaction_completed": runData.TransactionCompleted,
		"transaction_uuid":      runData.TransactionUUID,
	})

	return results, nil
}
<file_sep>package main

import (
	"context"

	"github.com/kolide/osquery-go/plugin/table"
)

type ResourceStatus struct {
	Title        string `yaml:"title"`
	File         string `yaml:"file"`
	Line         string `yaml:"line"`
	Resource     string `yaml:"resource"`
	ResourceType string `yaml:"resource_type"`
	// ContainmentPath []string `yaml:"containment_path"`
	EvaulationTime string `yaml:"evaluation_time"`
	// Tags             interface{}   `yaml:"tags"`
	// Time           string `yaml:"time"`
	Failed         string `yaml:"failed"`
	Changed        string `yaml:"changed"`
	OutOfSync      string `yaml:"out_of_sync"`
	Skipped        string `yaml:"skipped"`
	ChangeCount    string `yaml:"change_count"`
	OutOfSyncCount string `yaml:"out_of_sync_count"`
	// Events           []interface{} `yaml:"events"`
	CorrectiveChange string `yaml:"corrective_change"`
}

// Columns returns the type hinted columns for the logged in user.
func PuppetStateColumns() []table.ColumnDefinition {
	return []table.ColumnDefinition{
		table.TextColumn("title"),
		table.TextColumn("file"),
		table.TextColumn("line"),
		table.TextColumn("resource"),
		table.TextColumn("resource_type"),
		// table.TextColumn("containment_path"),
		table.TextColumn("evaluation_time"),
		// table.TextColumn("tags"),
		// table.TextColumn("time"),
		table.TextColumn("failed"),
		table.TextColumn("changed"),
		table.TextColumn("out_of_sync"),
		table.TextColumn("skipped"),
		table.TextColumn("change_count"),
		table.TextColumn("out_of_sync_count"),
		// table.TextColumn("events"),
		table.TextColumn("corrective_change"),
	}
}

func PuppetStateGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
	var results []map[string]string
	// fmt.Println("Generating info...")
	runData, err := GetPuppetYaml()
	// fmt.Printf("%s", runData)
	if err != nil {
		// fmt.Print(err)
		return results, err
	}

	for _, item := range runData.ResourceStatuses {
		results = append(results, map[string]string{
			"title":         item.Title,
			"file":          item.File,
			"line":          item.Line,
			"resource":      item.Resource,
			"resource_type": item.ResourceType,
			// "containment_path":    parseList(item.ContainmentPath),
			"evaluation_time": item.EvaulationTime,
			// "tags": item.Tags,
			// "time":              item.Time,
			"failed":            item.Failed,
			"changed":           item.Changed,
			"out_of_sync":       item.OutOfSync,
			"skipped":           item.Skipped,
			"change_count":      item.ChangeCount,
			"out_of_sync_count": item.OutOfSyncCount,
			// "events": item.Events,
			"corrective_change": item.CorrectiveChange,
		})
	}

	return results, nil
}
