Working commit
This commit is contained in:
parent
2eec2e5c9b
commit
60f45e6245
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,3 +16,4 @@
|
|||||||
# vendor/
|
# vendor/
|
||||||
|
|
||||||
dist/
|
dist/
|
||||||
|
notify-to-slack
|
||||||
|
10
Makefile
10
Makefile
@ -19,7 +19,7 @@ all-linux: $(filter dist/$(APP_NAME)-linux-%,$(TARGETS))
|
|||||||
# Build notify-to-slack for the current machine
|
# Build notify-to-slack for the current machine
|
||||||
$(APP_NAME): $(GOFILES)
|
$(APP_NAME): $(GOFILES)
|
||||||
@echo Version: $(VERSION)
|
@echo Version: $(VERSION)
|
||||||
go build -ldflags '-X "main.version=VERSION"' -o $(APP_NAME)
|
go build -ldflags '-X "main.version=$(VERSION)"' -o $(APP_NAME)
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: $(APP_NAME)
|
build: $(APP_NAME)
|
||||||
@ -30,7 +30,7 @@ test:
|
|||||||
go test -coverprofile=coverage.out
|
go test -coverprofile=coverage.out
|
||||||
go tool cover -func=coverage.out
|
go tool cover -func=coverage.out
|
||||||
@go tool cover -func=coverage.out | awk -v target=80.0% \
|
@go tool cover -func=coverage.out | awk -v target=80.0% \
|
||||||
'/^total:/ { print "Total coverage: " $3 " Minimum coverage: " target; if ($3+0.0 >= target+0.0) print "ok"; else { print "fail"; exit 1; } }'
|
'/^total:/ { print "Total coverage: " $$3 " Minimum coverage: " target; if ($$3+0.0 >= target+0.0) print "ok"; else { print "fail"; exit 1; } }'
|
||||||
|
|
||||||
# Installs pre-commit hooks
|
# Installs pre-commit hooks
|
||||||
.PHONY: install-hooks
|
.PHONY: install-hooks
|
||||||
@ -52,9 +52,9 @@ clean:
|
|||||||
$(TARGETS): $(GOFILES)
|
$(TARGETS): $(GOFILES)
|
||||||
mkdir -p ./dist
|
mkdir -p ./dist
|
||||||
GOOS=$(word 2, $(subst -, ,$(@))) GOARCH=$(word 3, $(subst -, ,$(@))) CGO_ENABLED=0 \
|
GOOS=$(word 2, $(subst -, ,$(@))) GOARCH=$(word 3, $(subst -, ,$(@))) CGO_ENABLED=0 \
|
||||||
go build -ldflags '-X "main.version=VERSION"' -a -installsuffix nocgo \
|
go build -ldflags '-X "main.version=$(VERSION)"' -a -installsuffix nocgo \
|
||||||
-o @
|
-o $@
|
||||||
|
|
||||||
.PHONY: $(TARGET_ALIAS)
|
.PHONY: $(TARGET_ALIAS)
|
||||||
$(TARGET_ALIAS):
|
$(TARGET_ALIAS):
|
||||||
$(MAKE) $(addprefix dist/,@)
|
$(MAKE) $(addprefix dist/,$@)
|
||||||
|
107
figures/figures.go
Normal file
107
figures/figures.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package figures
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ConfigFileNotFoundErr = errors.New("config file for provided path not found")
|
||||||
|
|
||||||
|
// fileExists checks if a file exists at a given path
|
||||||
|
func fileExists(path string) bool {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type figuration struct {
|
||||||
|
applicationName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFiguration creates a new figuration for the provided application
|
||||||
|
func NewFiguration(applicationName string) figuration {
|
||||||
|
return figuration{
|
||||||
|
applicationName: applicationName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigFilePath returns the path of a given file within the UserConfigDir.
|
||||||
|
func (fig figuration) GetConfigFilePath(filename string) (string, error) {
|
||||||
|
configDir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error getting current config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get or make config dir path
|
||||||
|
configDir = filepath.Join(configDir, fig.applicationName)
|
||||||
|
_ = os.MkdirAll(configDir, 0o755)
|
||||||
|
|
||||||
|
// Get the path to the provided file name within the config directory
|
||||||
|
configFilePath := filepath.Join(configDir, filename)
|
||||||
|
|
||||||
|
return configFilePath, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadConfig populates a provided struct with the data in config file of a given name
|
||||||
|
func (fig figuration) ReadConfig(name string, data interface{}) error {
|
||||||
|
configPath, err := fig.GetConfigFilePath(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fileExists(configPath) {
|
||||||
|
return fmt.Errorf("error reading config from file %s: %w", name, ConfigFileNotFoundErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := ioutil.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading config from file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(content, data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed parsing json from config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteConfig writes data from the provided struct to the config file with the provided name
|
||||||
|
func (fig figuration) WriteConfig(name string, data interface{}) error {
|
||||||
|
configPath, err := fig.GetConfigFilePath(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
contents, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed converting config to json: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ioutil.WriteFile(configPath, contents, 0o600); err != nil {
|
||||||
|
return fmt.Errorf("error writing config to file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config interface {
|
||||||
|
ApplicationName() string
|
||||||
|
Filename() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadConfig populates the provided struct matching the Config interface with config values from the user's config store
|
||||||
|
func ReadConfig(config Config) error {
|
||||||
|
return NewFiguration(config.ApplicationName()).ReadConfig(config.Filename(), config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteConfig writes the provided struct matching the Config interface to the users config store
|
||||||
|
func WriteConfig(config Config) error {
|
||||||
|
return NewFiguration(config.ApplicationName()).WriteConfig(config.Filename(), config)
|
||||||
|
}
|
9
go.mod
9
go.mod
@ -1,6 +1,13 @@
|
|||||||
module /iamthefij/notify-to-slack
|
module git.iamthefij.com/iamthefij/notify-to-slack
|
||||||
|
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/karrick/golf v1.4.0
|
||||||
|
github.com/slack-go/slack v0.10.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/websocket v1.4.2 // indirect
|
||||||
|
github.com/pkg/errors v0.8.0 // indirect
|
||||||
)
|
)
|
||||||
|
16
go.sum
Normal file
16
go.sum
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
|
||||||
|
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/karrick/golf v1.4.0 h1:9i9HnUh7uCyUFJhIqg311HBibw4f2pbGldi0ZM2FhaQ=
|
||||||
|
github.com/karrick/golf v1.4.0/go.mod h1:qGN0IhcEL+IEgCXp00RvH32UP59vtwc8w5YcIdArNRk=
|
||||||
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/slack-go/slack v0.10.0 h1:L16Eqg3QZzRKGXIVsFSZdJdygjOphb2FjRUwH6VrFu8=
|
||||||
|
github.com/slack-go/slack v0.10.0/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ=
|
||||||
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
185
main.go
185
main.go
@ -1,18 +1,183 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
var (
|
import (
|
||||||
// version of notify-to-slack being run
|
"encoding/json"
|
||||||
version = "dev"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/karrick/golf"
|
||||||
|
"github.com/slack-go/slack"
|
||||||
|
|
||||||
|
"git.iamthefij.com/iamthefij/notify-to-slack/figures"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
var (
|
||||||
showVersion := flag.Bool("version", false, "Display the version of minitor and exit")
|
applicationName = "notify-to-slack"
|
||||||
flag.Parse()
|
version = "dev"
|
||||||
|
// hookURL = "https://hooks.slack.com/services/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"
|
||||||
|
)
|
||||||
|
|
||||||
// Print version if flag is provided
|
type Config struct {
|
||||||
if *showVersion {
|
HookURL string
|
||||||
fmt.Println("notify-to-slack version:", version)
|
}
|
||||||
|
|
||||||
return
|
func (_ Config) ApplicationName() string {
|
||||||
|
return applicationName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ Config) Filename() string {
|
||||||
|
return "config"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShellCommand takes a string and constructs a command that executs within `sh`
|
||||||
|
func ShellCommand(command string) *exec.Cmd {
|
||||||
|
shellCommand := []string{"sh", "-c", strings.TrimSpace(command)}
|
||||||
|
|
||||||
|
return exec.Command(shellCommand[0], shellCommand[1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func maybeHostname() string {
|
||||||
|
if hostname, err := os.Hostname(); err != nil {
|
||||||
|
return "unknown hostname"
|
||||||
|
} else {
|
||||||
|
return hostname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func maybeUsername() string {
|
||||||
|
if currentUser, err := user.Current(); err != nil {
|
||||||
|
return "unknown user"
|
||||||
|
} else {
|
||||||
|
return currentUser.Username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printUsage() {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
||||||
|
fmt.Fprintf(os.Stderr, "Reads in and does stuff\n")
|
||||||
|
configPath, err := figures.NewFiguration(applicationName).GetConfigFilePath("")
|
||||||
|
if err == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Config directory is %s\n", configPath)
|
||||||
|
}
|
||||||
|
golf.PrintDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func printVersion() {
|
||||||
|
// fmt.Fprintf(os.Stderr, "%s: at version %s\n", os.Args[0], version)
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: at version %s\n", os.Args[0], version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
showHelp := golf.BoolP('h', "help", false, "show usage and exit")
|
||||||
|
showVersion := golf.BoolP('V', "version", false, "show version and exit")
|
||||||
|
lastStatus := golf.IntP('s', "status", -1, "last command status")
|
||||||
|
command := golf.StringP('c', "command", "", "execute command in a sub shell and publish results")
|
||||||
|
atChannel := golf.BoolP('a', "at-channel", false, "add @channel to the message")
|
||||||
|
channel := golf.StringP('l', "channel", "", "select channel to send message to, if not the default for your integration")
|
||||||
|
hookURL := golf.StringP('u', "hook-url", "", "set the webhook URL to use")
|
||||||
|
golf.Parse()
|
||||||
|
|
||||||
|
if *showHelp {
|
||||||
|
printUsage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *showVersion {
|
||||||
|
printVersion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the configuration
|
||||||
|
config := Config{}
|
||||||
|
err := figures.ReadConfig(&config)
|
||||||
|
|
||||||
|
// User set a new hook url
|
||||||
|
if *hookURL != "" {
|
||||||
|
config.HookURL = *hookURL
|
||||||
|
err = figures.WriteConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Could not write to config file: %s", err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil && !errors.Is(err, figures.ConfigFileNotFoundErr) {
|
||||||
|
panic(fmt.Sprintf("Error attempting to read the config file. %s", err))
|
||||||
|
}
|
||||||
|
if config.HookURL == "" {
|
||||||
|
panic("You have not set a hook url. Try running with --hook-url <your url here>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attachments := []slack.Attachment{}
|
||||||
|
|
||||||
|
// Execute nested command
|
||||||
|
if *command != "" {
|
||||||
|
c := ShellCommand(*command)
|
||||||
|
var footer string
|
||||||
|
output, err := c.CombinedOutput()
|
||||||
|
color := "good"
|
||||||
|
if err != nil {
|
||||||
|
color = "danger"
|
||||||
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
|
footer = fmt.Sprintf("status %d", exitError.ExitCode())
|
||||||
|
} else {
|
||||||
|
footer = fmt.Sprintf("unknown status %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attachments = append(attachments, slack.Attachment{
|
||||||
|
Color: color,
|
||||||
|
AuthorName: maybeUsername(),
|
||||||
|
AuthorSubname: maybeHostname(),
|
||||||
|
Text: string(output),
|
||||||
|
Footer: footer,
|
||||||
|
Ts: json.Number(strconv.FormatInt(time.Now().Unix(), 10)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get message text from provided arguments
|
||||||
|
args := golf.Args()
|
||||||
|
message := strings.Join(args, " ")
|
||||||
|
|
||||||
|
// Build status attachment if a status was provided
|
||||||
|
if *lastStatus >= 0 {
|
||||||
|
color := "good"
|
||||||
|
if *lastStatus > 0 {
|
||||||
|
color = "danger"
|
||||||
|
}
|
||||||
|
|
||||||
|
attachments = append(attachments, slack.Attachment{
|
||||||
|
Color: color,
|
||||||
|
AuthorName: maybeUsername(),
|
||||||
|
AuthorSubname: maybeHostname(),
|
||||||
|
Text: message,
|
||||||
|
Footer: fmt.Sprintf("status %d", *lastStatus),
|
||||||
|
Ts: json.Number(strconv.FormatInt(time.Now().Unix(), 10)),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Empty out message to avoid duplicating in the message content
|
||||||
|
message = ""
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe prepend an @channel
|
||||||
|
if *atChannel {
|
||||||
|
message = strings.Join([]string{"<!channel>", message}, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := slack.WebhookMessage{
|
||||||
|
Attachments: attachments,
|
||||||
|
Username: "cli-noti",
|
||||||
|
Text: message,
|
||||||
|
Channel: *channel,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = slack.PostWebhook(config.HookURL, &msg)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user