Working commit

This commit is contained in:
IamTheFij 2021-12-14 09:40:45 -08:00
parent 2eec2e5c9b
commit 60f45e6245
6 changed files with 312 additions and 16 deletions

1
.gitignore vendored
View File

@ -16,3 +16,4 @@
# vendor/ # vendor/
dist/ dist/
notify-to-slack

View File

@ -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
View 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
View File

@ -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
View 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
View File

@ -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)
} }
} }