(v0.1.1) Automated packaging of release by Packagr
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Jason Kulatunga
|
||||
|
||||
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.
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"github.com/kvz/logstreamer"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
)
|
||||
|
||||
//http://craigwickesser.com/2015/02/golang-cmd-with-custom-environment/
|
||||
//http://www.ryanday.net/2012/10/01/installing-go-and-gopath/
|
||||
//
|
||||
|
||||
func BashCmdExec(cmd string, workingDir string, environ []string, logPrefix string) error {
|
||||
return CmdExec("sh", []string{"-c", cmd}, workingDir, environ, logPrefix)
|
||||
}
|
||||
|
||||
func CmdExec(cmdName string, cmdArgs []string, workingDir string, environ []string, logPrefix string) error {
|
||||
if logPrefix == "" {
|
||||
logPrefix = " >> "
|
||||
} else {
|
||||
logPrefix = logPrefix + " | "
|
||||
}
|
||||
|
||||
// Create a logger (your app probably already has one)
|
||||
logger := log.New(os.Stdout, logPrefix, log.Ldate|log.Ltime)
|
||||
|
||||
// Setup a streamer that we'll pipe cmd.Stdout to
|
||||
logStreamerOut := logstreamer.NewLogstreamer(logger, "stdout", false)
|
||||
defer logStreamerOut.Close()
|
||||
// Setup a streamer that we'll pipe cmd.Stderr to.
|
||||
// We want to record/buffer anything that's written to this (3rd argument true)
|
||||
logStreamerErr := logstreamer.NewLogstreamer(logger, "stderr", true)
|
||||
defer logStreamerErr.Close()
|
||||
|
||||
cmd := exec.Command(cmdName, cmdArgs...)
|
||||
cmd.Stdout = logStreamerOut
|
||||
cmd.Stderr = logStreamerErr
|
||||
if environ != nil {
|
||||
cmd.Env = environ
|
||||
}
|
||||
if workingDir != "" && path.IsAbs(workingDir) {
|
||||
cmd.Dir = workingDir
|
||||
} else if workingDir != "" {
|
||||
return stderrors.New("Working Directory must be an absolute path")
|
||||
}
|
||||
//cmdReader, err := cmd.StdoutPipe()
|
||||
//if err != nil {
|
||||
// fmt.Fprintln(os.Stderr, "Error creating StdoutPipe for Cmd", err)
|
||||
// return err
|
||||
//}
|
||||
//
|
||||
//done := make(chan struct{})
|
||||
//
|
||||
//scanner := bufio.NewScanner(cmdReader)
|
||||
//go func() {
|
||||
// for scanner.Scan() {
|
||||
// fmt.Printf("%s%s\n", logPrefix, scanner.Text())
|
||||
// }
|
||||
// done <- struct{}{}
|
||||
//
|
||||
//}()
|
||||
|
||||
// Reset any error we recorded
|
||||
logStreamerErr.FlushRecord()
|
||||
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error starting Cmd", err)
|
||||
return err
|
||||
}
|
||||
|
||||
//<-done
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error waiting for Cmd", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fatih/color"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func ExpandPath(filePath string) (string, error) {
|
||||
filePath, err := homedir.Expand(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
filePath, err = filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
func FileWrite(filePath string, content string, perm os.FileMode, dryRun bool) error {
|
||||
filePath, err := ExpandPath(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
fmt.Printf("%v %v %v:\n",
|
||||
color.GreenString("[DRYRUN]"),
|
||||
"Would have written content to",
|
||||
color.GreenString(filePath),
|
||||
)
|
||||
color.Green(content)
|
||||
} else {
|
||||
d1 := []byte(content)
|
||||
err = ioutil.WriteFile(filePath, d1, perm)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func FileExists(filePath string) bool {
|
||||
filePath, err := ExpandPath(filePath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func FileDelete(filePath string) error {
|
||||
filePath, err := ExpandPath(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Remove(filePath)
|
||||
}
|
||||
|
||||
// CopyFile copies the contents of the file named src to the file named
|
||||
// by dst. The file will be created if it does not already exist. If the
|
||||
// destination file exists, all it's contents will be replaced by the contents
|
||||
// of the source file. The file mode will be copied from the source and
|
||||
// the copied data is synced/flushed to stable storage.
|
||||
func CopyFile(src, dst string) (err error) {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer in.Close()
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if e := out.Close(); e != nil {
|
||||
err = e
|
||||
}
|
||||
}()
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = out.Sync()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = os.Chmod(dst, si.Mode())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
|
||||
// Source directory must exist, destination directory must *not* exist.
|
||||
// Symlinks are ignored and skipped.
|
||||
func CopyDir(src string, dst string) (err error) {
|
||||
src = filepath.Clean(src)
|
||||
dst = filepath.Clean(dst)
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !si.IsDir() {
|
||||
return fmt.Errorf("source is not a directory")
|
||||
}
|
||||
_, err = os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
return fmt.Errorf("destination already exists")
|
||||
}
|
||||
err = os.MkdirAll(dst, si.Mode())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
entries, err := ioutil.ReadDir(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, entry := range entries {
|
||||
srcPath := filepath.Join(src, entry.Name())
|
||||
dstPath := filepath.Join(dst, entry.Name())
|
||||
if entry.IsDir() {
|
||||
err = CopyDir(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Skip symlinks.
|
||||
if entry.Mode()&os.ModeSymlink != 0 {
|
||||
continue
|
||||
}
|
||||
err = CopyFile(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func MapKeys(m map[string]interface{}) []string {
|
||||
var keys []string
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
func MapDeepCopy(m map[string]interface{}) (map[string]interface{}, error) {
|
||||
jsonString, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nm := map[string]interface{}{}
|
||||
err = json.Unmarshal([]byte(jsonString), &nm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nm, nil
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package utils
|
||||
|
||||
func SliceIncludes(slice []string, item string) bool {
|
||||
for _, val := range slice {
|
||||
if val == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//func indexOf(answers []interface{}, item interface{}) (int) {
|
||||
// for k, v := range answers {
|
||||
// if v == item {
|
||||
// return k
|
||||
// }
|
||||
// }
|
||||
// return -1
|
||||
//}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/fatih/color"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func StdinQueryPassword(question string) (string, error) {
|
||||
|
||||
fmt.Println(color.BlueString(question))
|
||||
bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
text := strings.TrimSpace(string(bytePassword))
|
||||
return text, nil
|
||||
}
|
||||
|
||||
func StdinQuery(question string) string {
|
||||
|
||||
fmt.Println(color.BlueString(question))
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
text, _ := reader.ReadString('\n')
|
||||
text = strings.TrimSpace(text)
|
||||
return text
|
||||
}
|
||||
|
||||
func StdinQueryBoolean(question string) bool {
|
||||
|
||||
text := StdinQuery(question)
|
||||
text = strings.ToLower(text)
|
||||
|
||||
if text == "true" || text == "yes" || text == "y" {
|
||||
return true
|
||||
} else if text == "false" || text == "no" || text == "n" {
|
||||
return false
|
||||
} else {
|
||||
color.Yellow("WARNING: invalid response only true/yes/y/false/no/n allowed not `%v`.\nAssuming `no`", text)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func StdinQueryInt(question string) (int, error) {
|
||||
|
||||
text := StdinQuery(question)
|
||||
return StringToInt(text)
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func StringToInt(input string) (int, error) {
|
||||
i, err := strconv.Atoi(input)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func SnakeCaseToCamelCase(inputUnderScoreStr string) (camelCase string) {
|
||||
//snake_case to camelCase
|
||||
|
||||
isToUpper := false
|
||||
|
||||
for k, v := range inputUnderScoreStr {
|
||||
if k == 0 {
|
||||
camelCase = strings.ToUpper(string(inputUnderScoreStr[0]))
|
||||
} else {
|
||||
if isToUpper {
|
||||
camelCase += strings.ToUpper(string(v))
|
||||
isToUpper = false
|
||||
} else {
|
||||
if v == '_' {
|
||||
isToUpper = true
|
||||
} else {
|
||||
camelCase += string(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// https://github.com/DaddyOh/golang-samples/blob/master/pad.go
|
||||
/*
|
||||
* leftPad and rightPad just repoeat the padStr the indicated
|
||||
* number of times
|
||||
*
|
||||
*/
|
||||
|
||||
func LeftPad(s string, padStr string, pLen int) string {
|
||||
return strings.Repeat(padStr, pLen) + s
|
||||
}
|
||||
func RightPad(s string, padStr string, pLen int) string {
|
||||
return s + strings.Repeat(padStr, pLen)
|
||||
}
|
||||
|
||||
/* the Pad2Len functions are generally assumed to be padded with short sequences of strings
|
||||
* in many cases with a single character sequence
|
||||
*
|
||||
* so we assume we can build the string out as if the char seq is 1 char and then
|
||||
* just substr the string if it is longer than needed
|
||||
*
|
||||
* this means we are wasting some cpu and memory work
|
||||
* but this always get us to want we want it to be
|
||||
*
|
||||
* in short not optimized to for massive string work
|
||||
*
|
||||
* If the overallLen is shorter than the original string length
|
||||
* the string will be shortened to this length (substr)
|
||||
*
|
||||
*/
|
||||
|
||||
func RightPad2Len(s string, padStr string, overallLen int) string {
|
||||
padCountInt := 1 + ((overallLen - len(padStr)) / len(padStr))
|
||||
var retStr = s + strings.Repeat(padStr, padCountInt)
|
||||
return retStr[:overallLen]
|
||||
}
|
||||
func LeftPad2Len(s string, padStr string, overallLen int) string {
|
||||
padCountInt := 1 + ((overallLen - len(padStr)) / len(padStr))
|
||||
var retStr = strings.Repeat(padStr, padCountInt) + s
|
||||
return retStr[(len(retStr) - overallLen):]
|
||||
}
|
||||
|
||||
func StripIndent(multilineStr string) string {
|
||||
return strings.Replace(multilineStr, "\t", "", -1)
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"hash/fnv"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
|
||||
func PopulatePathTemplate(pathTmplContent string, data interface{}) (string, error){
|
||||
tmplFilepath, err := PopulateTemplate(pathTmplContent, data)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
tmplFilepath, err = ExpandPath(tmplFilepath)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
return tmplFilepath, nil
|
||||
}
|
||||
|
||||
|
||||
func PopulateTemplate(tmplContent string, data interface{}) (string, error) {
|
||||
//set functions
|
||||
fns := template.FuncMap{
|
||||
"uniquePort": UniquePort,
|
||||
"expandPath": ExpandPath,
|
||||
}
|
||||
|
||||
// prep the template, set the option
|
||||
tmpl, err := template.New("populate").Option("missingkey=error").Funcs(fns).Parse(tmplContent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
//specify that any missing keys in the template will throw an error
|
||||
buf := new(bytes.Buffer)
|
||||
err = tmpl.Execute(buf, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
//convert buffered content to string
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// https://play.golang.org/p/k8bws03uid
|
||||
func UniquePort(data interface{}) (int, error) {
|
||||
|
||||
var contentData []byte
|
||||
switch in := data.(type) {
|
||||
case string:
|
||||
contentData = []byte(in)
|
||||
default:
|
||||
jsonData, err := json.Marshal(StringifyYAMLMapKeys(in))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
contentData = jsonData
|
||||
}
|
||||
|
||||
hash := fnv.New32a()
|
||||
hash.Write(contentData)
|
||||
|
||||
//last port - last privileged port.
|
||||
portRange := 65535 - 1023
|
||||
|
||||
uniquePort := (hash.Sum32() % uint32(portRange)) + 1023
|
||||
return int(uniquePort), nil
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package utils
|
||||
|
||||
import "fmt"
|
||||
|
||||
// stringifyKeysMapValue recurses into in and changes all instances of
|
||||
// map[interface{}]interface{} to map[string]interface{}. This is useful to
|
||||
// work around the impedence mismatch between JSON and YAML unmarshaling that's
|
||||
// described here: https://github.com/go-yaml/yaml/issues/139
|
||||
//
|
||||
// Inspired by https://github.com/stripe/stripe-mock, MIT licensed
|
||||
func StringifyYAMLMapKeys(in interface{}) interface{} {
|
||||
switch in := in.(type) {
|
||||
case []interface{}:
|
||||
res := make([]interface{}, len(in))
|
||||
for i, v := range in {
|
||||
res[i] = StringifyYAMLMapKeys(v)
|
||||
}
|
||||
return res
|
||||
case map[string]interface{}:
|
||||
res := make(map[string]interface{})
|
||||
for k, v := range in {
|
||||
res[k] = StringifyYAMLMapKeys(v)
|
||||
}
|
||||
return res
|
||||
case map[interface{}]interface{}:
|
||||
res := make(map[string]interface{})
|
||||
for k, v := range in {
|
||||
res[fmt.Sprintf("%v", k)] = StringifyYAMLMapKeys(v)
|
||||
}
|
||||
return res
|
||||
default:
|
||||
return in
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user