2017-01-03 22:27:20 -08:00
|
|
|
package common
|
|
|
|
|
|
|
|
|
|
import (
|
2021-01-01 21:13:39 -05:00
|
|
|
"context"
|
2021-02-01 22:23:05 -05:00
|
|
|
"encoding/json"
|
2020-03-10 14:14:37 -04:00
|
|
|
"errors"
|
2017-01-03 22:27:20 -08:00
|
|
|
"fmt"
|
2017-04-24 09:03:30 -06:00
|
|
|
"io/ioutil"
|
2017-01-03 22:27:20 -08:00
|
|
|
"os"
|
2020-02-03 04:37:31 -05:00
|
|
|
"path/filepath"
|
2017-01-03 22:27:20 -08:00
|
|
|
"regexp"
|
2020-02-09 20:37:03 -05:00
|
|
|
"sort"
|
2020-11-01 15:53:53 -05:00
|
|
|
"strconv"
|
2017-01-03 22:27:20 -08:00
|
|
|
"strings"
|
2017-10-04 00:48:02 -04:00
|
|
|
"unicode"
|
2017-01-03 22:27:20 -08:00
|
|
|
|
2020-11-01 15:51:24 -05:00
|
|
|
"github.com/ryanuber/columnize"
|
2021-01-01 21:13:39 -05:00
|
|
|
"golang.org/x/sync/errgroup"
|
2017-01-03 22:27:20 -08:00
|
|
|
)
|
|
|
|
|
|
2020-03-10 23:06:00 -04:00
|
|
|
type errfunc func() error
|
|
|
|
|
|
2021-02-13 00:46:35 -05:00
|
|
|
var (
|
|
|
|
|
// DefaultProperties is a map of all valid common properties with corresponding default property values
|
|
|
|
|
DefaultProperties = map[string]string{
|
|
|
|
|
"deployed": "false",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GlobalProperties is a map of all valid global common properties
|
|
|
|
|
GlobalProperties = map[string]bool{}
|
|
|
|
|
)
|
|
|
|
|
|
2020-03-10 14:12:05 -04:00
|
|
|
// AppRoot returns the app root path
|
|
|
|
|
func AppRoot(appName string) string {
|
|
|
|
|
dokkuRoot := MustGetEnv("DOKKU_ROOT")
|
|
|
|
|
return fmt.Sprintf("%v/%v", dokkuRoot, appName)
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-31 18:44:00 -04:00
|
|
|
// AppHostRoot returns the app root path
|
2020-07-17 19:45:05 -04:00
|
|
|
func AppHostRoot(appName string) string {
|
|
|
|
|
dokkuHostRoot := MustGetEnv("DOKKU_HOST_ROOT")
|
|
|
|
|
return fmt.Sprintf("%v/%v", dokkuHostRoot, appName)
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-10 23:56:16 -04:00
|
|
|
// AskForDestructiveConfirmation checks for confirmation on destructive actions
|
2020-03-10 14:13:11 -04:00
|
|
|
func AskForDestructiveConfirmation(name string, objectType string) error {
|
|
|
|
|
LogWarn("WARNING: Potentially Destructive Action")
|
|
|
|
|
LogWarn(fmt.Sprintf("This command will destroy %v %v.", objectType, name))
|
|
|
|
|
LogWarn(fmt.Sprintf("To proceed, type \"%v\"", name))
|
|
|
|
|
fmt.Print("> ")
|
|
|
|
|
var response string
|
|
|
|
|
_, err := fmt.Scanln(&response)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if response != name {
|
|
|
|
|
LogStderr("Confirmation did not match test. Aborted.")
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-27 22:41:57 -04:00
|
|
|
// CommandUsage outputs help for a command
|
|
|
|
|
func CommandUsage(helpHeader string, helpContent string) {
|
|
|
|
|
config := columnize.DefaultConfig()
|
|
|
|
|
config.Delim = ","
|
|
|
|
|
config.Prefix = " "
|
|
|
|
|
config.Empty = ""
|
|
|
|
|
content := strings.Split(helpContent, "\n")[1:]
|
|
|
|
|
fmt.Println(helpHeader)
|
|
|
|
|
fmt.Println(columnize.Format(content, config))
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-10 14:14:37 -04:00
|
|
|
// GetAppScheduler fetches the scheduler for a given application
|
|
|
|
|
func GetAppScheduler(appName string) string {
|
2020-12-29 23:55:13 -05:00
|
|
|
appScheduler := ""
|
|
|
|
|
globalScheduler := ""
|
|
|
|
|
|
2021-01-01 21:13:39 -05:00
|
|
|
ctx := context.Background()
|
|
|
|
|
errs, ctx := errgroup.WithContext(ctx)
|
2020-12-29 23:55:13 -05:00
|
|
|
|
|
|
|
|
if appName != "--global" {
|
2021-01-01 21:13:39 -05:00
|
|
|
errs.Go(func() error {
|
2021-01-02 06:11:34 -05:00
|
|
|
appScheduler = getAppScheduler(appName)
|
2021-01-01 21:13:39 -05:00
|
|
|
return nil
|
|
|
|
|
})
|
2020-03-10 14:14:37 -04:00
|
|
|
}
|
2021-01-01 21:13:39 -05:00
|
|
|
errs.Go(func() error {
|
2020-12-29 23:55:13 -05:00
|
|
|
globalScheduler = GetGlobalScheduler()
|
2021-01-01 21:13:39 -05:00
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
errs.Wait()
|
2020-03-10 14:14:37 -04:00
|
|
|
|
2020-12-29 23:55:13 -05:00
|
|
|
if appScheduler == "" {
|
|
|
|
|
appScheduler = globalScheduler
|
|
|
|
|
}
|
|
|
|
|
return appScheduler
|
2020-11-21 20:56:59 -05:00
|
|
|
}
|
|
|
|
|
|
2021-01-02 06:11:34 -05:00
|
|
|
func getAppScheduler(appName string) string {
|
|
|
|
|
b, _ := PlugnTriggerOutput("config-get", []string{appName, "DOKKU_SCHEDULER"}...)
|
|
|
|
|
value := strings.TrimSpace(string(b[:]))
|
|
|
|
|
if value != "" {
|
|
|
|
|
return value
|
|
|
|
|
}
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-21 20:56:59 -05:00
|
|
|
// GetGlobalScheduler fetchs the global scheduler
|
|
|
|
|
func GetGlobalScheduler() string {
|
|
|
|
|
b, _ := PlugnTriggerOutput("config-get-global", []string{"DOKKU_SCHEDULER"}...)
|
2020-12-13 02:04:35 -05:00
|
|
|
value := strings.TrimSpace(string(b[:]))
|
2020-03-10 14:14:37 -04:00
|
|
|
if value != "" {
|
|
|
|
|
return value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "docker-local"
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-03 22:27:20 -08:00
|
|
|
// GetDeployingAppImageName returns deploying image identifier for a given app, tag tuple. validate if tag is presented
|
2020-11-23 00:11:00 -05:00
|
|
|
func GetDeployingAppImageName(appName, imageTag, imageRepo string) (string, error) {
|
2021-01-01 21:14:33 -05:00
|
|
|
imageRemoteRepository := ""
|
|
|
|
|
newImageTag := ""
|
|
|
|
|
newImageRepo := ""
|
2017-01-03 22:27:20 -08:00
|
|
|
|
2021-01-01 21:14:33 -05:00
|
|
|
ctx := context.Background()
|
|
|
|
|
errs, ctx := errgroup.WithContext(ctx)
|
|
|
|
|
errs.Go(func() error {
|
|
|
|
|
b, err := PlugnTriggerOutput("deployed-app-repository", []string{appName}...)
|
|
|
|
|
if err == nil {
|
|
|
|
|
imageRemoteRepository = strings.TrimSpace(string(b[:]))
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
})
|
|
|
|
|
errs.Go(func() error {
|
|
|
|
|
b, err := PlugnTriggerOutput("deployed-app-image-tag", []string{appName}...)
|
|
|
|
|
if err == nil {
|
|
|
|
|
newImageTag = strings.TrimSpace(string(b[:]))
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
})
|
2017-01-03 22:27:20 -08:00
|
|
|
|
2021-01-01 21:14:33 -05:00
|
|
|
errs.Go(func() error {
|
|
|
|
|
b, err := PlugnTriggerOutput("deployed-app-image-repo", []string{appName}...)
|
|
|
|
|
if err == nil {
|
2021-01-02 18:47:48 -05:00
|
|
|
newImageRepo = strings.TrimSpace(string(b[:]))
|
2021-01-01 21:14:33 -05:00
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if err := errs.Wait(); err != nil {
|
2020-11-23 00:11:00 -05:00
|
|
|
return "", err
|
2017-01-03 22:27:20 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if newImageRepo != "" {
|
|
|
|
|
imageRepo = newImageRepo
|
|
|
|
|
}
|
|
|
|
|
if newImageTag != "" {
|
|
|
|
|
imageTag = newImageTag
|
|
|
|
|
}
|
|
|
|
|
if imageRepo == "" {
|
|
|
|
|
imageRepo = GetAppImageRepo(appName)
|
|
|
|
|
}
|
|
|
|
|
if imageTag == "" {
|
|
|
|
|
imageTag = "latest"
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-23 00:11:00 -05:00
|
|
|
imageName := fmt.Sprintf("%s%s:%s", imageRemoteRepository, imageRepo, imageTag)
|
2017-01-03 22:27:20 -08:00
|
|
|
if !VerifyImage(imageName) {
|
2020-11-23 00:11:00 -05:00
|
|
|
return "", fmt.Errorf("App image (%s) not found", imageName)
|
2017-01-03 22:27:20 -08:00
|
|
|
}
|
2020-11-23 00:11:00 -05:00
|
|
|
return imageName, nil
|
2017-01-03 22:27:20 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetAppImageRepo is the central definition of a dokku image repo pattern
|
|
|
|
|
func GetAppImageRepo(appName string) string {
|
|
|
|
|
return strings.Join([]string{"dokku", appName}, "/")
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-03 04:37:31 -05:00
|
|
|
// GetAppContainerIDs returns a list of docker container ids for given app and optional container_type
|
|
|
|
|
func GetAppContainerIDs(appName string, containerType string) ([]string, error) {
|
|
|
|
|
var containerIDs []string
|
2020-03-10 14:14:37 -04:00
|
|
|
appRoot := AppRoot(appName)
|
2020-02-03 04:37:31 -05:00
|
|
|
containerFilePath := fmt.Sprintf("%v/CONTAINER", appRoot)
|
|
|
|
|
_, err := os.Stat(containerFilePath)
|
|
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
|
containerIDs = append(containerIDs, ReadFirstLine(containerFilePath))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
containerPattern := fmt.Sprintf("%v/CONTAINER.*", appRoot)
|
|
|
|
|
if containerType != "" {
|
|
|
|
|
containerPattern = fmt.Sprintf("%v/CONTAINER.%v.*", appRoot, containerType)
|
|
|
|
|
if strings.Contains(".", containerType) {
|
|
|
|
|
containerPattern = fmt.Sprintf("%v/CONTAINER.%v", appRoot, containerType)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
files, _ := filepath.Glob(containerPattern)
|
|
|
|
|
for _, containerFile := range files {
|
|
|
|
|
containerIDs = append(containerIDs, ReadFirstLine(containerFile))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return containerIDs, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetAppRunningContainerIDs return a list of running docker container ids for given app and optional container_type
|
|
|
|
|
func GetAppRunningContainerIDs(appName string, containerType string) ([]string, error) {
|
|
|
|
|
var runningContainerIDs []string
|
|
|
|
|
if !IsDeployed(appName) {
|
|
|
|
|
LogFail(fmt.Sprintf("App %v has not been deployed", appName))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
containerIDs, err := GetAppContainerIDs(appName, containerType)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return runningContainerIDs, nil
|
|
|
|
|
}
|
|
|
|
|
for _, containerID := range containerIDs {
|
|
|
|
|
if ContainerIsRunning(containerID) {
|
|
|
|
|
runningContainerIDs = append(runningContainerIDs, containerID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return runningContainerIDs, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-10 14:14:37 -04:00
|
|
|
// GetRunningImageTag retrieves current image tag for a given app and returns empty string if no deployed containers are found
|
|
|
|
|
func GetRunningImageTag(appName string) (string, error) {
|
|
|
|
|
containerIDs, err := GetAppContainerIDs(appName, "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, containerID := range containerIDs {
|
|
|
|
|
if image, err := DockerInspect(containerID, "{{ .Config.Image }}"); err == nil {
|
|
|
|
|
return strings.Split(image, ":")[1], nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "", errors.New("No image tag found")
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-24 09:03:30 -06:00
|
|
|
// DokkuApps returns a list of all local apps
|
2017-10-02 16:50:05 -07:00
|
|
|
func DokkuApps() (apps []string, err error) {
|
2017-04-24 09:03:30 -06:00
|
|
|
dokkuRoot := MustGetEnv("DOKKU_ROOT")
|
|
|
|
|
files, err := ioutil.ReadDir(dokkuRoot)
|
|
|
|
|
if err != nil {
|
2017-10-02 16:50:05 -07:00
|
|
|
err = fmt.Errorf("You haven't deployed any applications yet")
|
|
|
|
|
return
|
2017-04-24 09:03:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, f := range files {
|
2020-03-10 14:14:37 -04:00
|
|
|
appRoot := AppRoot(f.Name())
|
2017-04-24 09:03:30 -06:00
|
|
|
if !DirectoryExists(appRoot) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2020-05-08 23:57:30 -04:00
|
|
|
if strings.HasPrefix(f.Name(), ".") {
|
2017-04-24 09:03:30 -06:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
apps = append(apps, f.Name())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(apps) == 0 {
|
2017-10-02 16:50:05 -07:00
|
|
|
err = fmt.Errorf("You haven't deployed any applications yet")
|
|
|
|
|
return
|
2017-04-24 09:03:30 -06:00
|
|
|
}
|
|
|
|
|
|
2017-10-02 16:50:05 -07:00
|
|
|
return
|
2017-04-24 09:03:30 -06:00
|
|
|
}
|
|
|
|
|
|
2017-04-26 18:49:19 -06:00
|
|
|
// GetAppImageName returns image identifier for a given app, tag tuple. validate if tag is presented
|
2017-04-24 09:03:30 -06:00
|
|
|
func GetAppImageName(appName, imageTag, imageRepo string) (imageName string) {
|
|
|
|
|
if imageRepo == "" {
|
|
|
|
|
imageRepo = GetAppImageRepo(appName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if imageTag == "" {
|
|
|
|
|
imageName = fmt.Sprintf("%v:latest", imageRepo)
|
|
|
|
|
} else {
|
|
|
|
|
imageName = fmt.Sprintf("%v:%v", imageRepo, imageTag)
|
|
|
|
|
if !VerifyImage(imageName) {
|
2018-04-03 01:21:50 -04:00
|
|
|
LogFail(fmt.Sprintf("App image (%s) not found", imageName))
|
2017-04-24 09:03:30 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-22 15:32:14 -06:00
|
|
|
// IsDeployed returns true if given app has a running container
|
2017-04-24 09:03:30 -06:00
|
|
|
func IsDeployed(appName string) bool {
|
2021-02-13 00:46:35 -05:00
|
|
|
deployed := PropertyGetDefault("common", appName, "deployed", "")
|
|
|
|
|
if deployed == "" {
|
|
|
|
|
deployed = "false"
|
|
|
|
|
scheduler := GetAppScheduler(appName)
|
|
|
|
|
_, err := PlugnTriggerOutput("scheduler-is-deployed", []string{scheduler, appName}...)
|
|
|
|
|
if err == nil {
|
|
|
|
|
deployed = "true"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CommandPropertySet("common", appName, "deployed", deployed, DefaultProperties, GlobalProperties)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return deployed == "true"
|
2017-01-03 22:27:20 -08:00
|
|
|
}
|
2017-04-24 09:03:30 -06:00
|
|
|
|
2017-09-03 19:34:44 -04:00
|
|
|
// MustGetEnv returns env variable or fails if it's not set
|
2017-10-02 16:50:05 -07:00
|
|
|
func MustGetEnv(key string) (val string) {
|
|
|
|
|
val = os.Getenv(key)
|
|
|
|
|
if val == "" {
|
2017-09-03 19:34:44 -04:00
|
|
|
LogFail(fmt.Sprintf("%s not set!", key))
|
|
|
|
|
}
|
2017-10-02 16:50:05 -07:00
|
|
|
return
|
2017-09-03 19:34:44 -04:00
|
|
|
}
|
|
|
|
|
|
2019-11-27 16:35:52 -05:00
|
|
|
// GetenvWithDefault returns env variable or defaultValue if it's not set
|
|
|
|
|
func GetenvWithDefault(key string, defaultValue string) (val string) {
|
|
|
|
|
val = os.Getenv(key)
|
|
|
|
|
if val == "" {
|
|
|
|
|
val = defaultValue
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-21 01:40:40 -05:00
|
|
|
// ParseReportArgs splits out flags from non-flags for input into report commands
|
2020-12-21 01:36:59 -05:00
|
|
|
func ParseReportArgs(pluginName string, arguments []string) ([]string, string, error) {
|
|
|
|
|
osArgs := []string{}
|
|
|
|
|
infoFlags := []string{}
|
2021-02-01 22:23:05 -05:00
|
|
|
skipNext := false
|
|
|
|
|
for i, argument := range arguments {
|
|
|
|
|
if skipNext {
|
|
|
|
|
skipNext = false
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if argument == "--format" {
|
|
|
|
|
osArgs = append(osArgs, argument, arguments[i+1])
|
|
|
|
|
skipNext = true
|
|
|
|
|
continue
|
|
|
|
|
}
|
2020-12-21 01:36:59 -05:00
|
|
|
if strings.HasPrefix(argument, "--") {
|
|
|
|
|
infoFlags = append(infoFlags, argument)
|
|
|
|
|
} else {
|
|
|
|
|
osArgs = append(osArgs, argument)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(infoFlags) == 0 {
|
|
|
|
|
return osArgs, "", nil
|
|
|
|
|
}
|
|
|
|
|
if len(infoFlags) == 1 {
|
|
|
|
|
return osArgs, infoFlags[0], nil
|
|
|
|
|
}
|
|
|
|
|
return osArgs, "", fmt.Errorf("%s:report command allows only a single flag", pluginName)
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-09 20:37:03 -05:00
|
|
|
// ReportSingleApp is an internal function that displays a report for an app
|
2021-02-01 22:23:05 -05:00
|
|
|
func ReportSingleApp(reportType string, appName string, infoFlag string, infoFlags map[string]string, infoFlagKeys []string, format string, trimPrefix bool, uppercaseFirstCharacter bool) error {
|
|
|
|
|
if format != "stdout" && infoFlag != "" {
|
|
|
|
|
return errors.New("--format flag cannot be specified when specifying an info flag")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if format == "json" {
|
|
|
|
|
data := map[string]string{}
|
|
|
|
|
for key, value := range infoFlags {
|
|
|
|
|
prefix := "--"
|
|
|
|
|
if trimPrefix {
|
|
|
|
|
prefix = fmt.Sprintf("--%v-", reportType)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// key = strings.Replace(strings.Replace(strings.TrimPrefix(key, prefix), "-", " ", -1), ".", " ", -1)
|
|
|
|
|
data[strings.TrimPrefix(key, prefix)] = value
|
|
|
|
|
}
|
|
|
|
|
out, err := json.Marshal(data)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
Log(string(out))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-09 20:37:03 -05:00
|
|
|
flags := []string{}
|
2020-02-09 20:47:20 -05:00
|
|
|
for key := range infoFlags {
|
2020-02-09 20:37:03 -05:00
|
|
|
flags = append(flags, key)
|
|
|
|
|
}
|
|
|
|
|
sort.Strings(flags)
|
|
|
|
|
|
|
|
|
|
if len(infoFlag) == 0 {
|
|
|
|
|
LogInfo2Quiet(fmt.Sprintf("%s %v information", appName, reportType))
|
|
|
|
|
for _, k := range flags {
|
2021-01-07 01:34:05 -05:00
|
|
|
v, ok := infoFlags[k]
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-09 20:37:03 -05:00
|
|
|
prefix := "--"
|
|
|
|
|
if trimPrefix {
|
|
|
|
|
prefix = fmt.Sprintf("--%v-", reportType)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
key := strings.Replace(strings.Replace(strings.TrimPrefix(k, prefix), "-", " ", -1), ".", " ", -1)
|
|
|
|
|
|
|
|
|
|
if uppercaseFirstCharacter {
|
|
|
|
|
key = UcFirst(key)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LogVerbose(fmt.Sprintf("%s%s", RightPad(fmt.Sprintf("%s:", key), 31, " "), v))
|
|
|
|
|
}
|
2020-02-22 06:29:14 -05:00
|
|
|
return nil
|
2020-02-09 20:37:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, k := range flags {
|
|
|
|
|
if infoFlag == k {
|
2021-01-07 01:34:05 -05:00
|
|
|
v, ok := infoFlags[k]
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2020-02-22 04:32:51 -05:00
|
|
|
fmt.Println(v)
|
2020-02-22 06:29:14 -05:00
|
|
|
return nil
|
2020-02-09 20:37:03 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-07 01:34:05 -05:00
|
|
|
sort.Strings(infoFlagKeys)
|
|
|
|
|
return fmt.Errorf("Invalid flag passed, valid flags: %s", strings.Join(infoFlagKeys, ", "))
|
2020-02-09 20:37:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RightPad right-pads the string with pad up to len runes
|
|
|
|
|
func RightPad(str string, length int, pad string) string {
|
|
|
|
|
return str + times(pad, length-len(str))
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-02 00:51:45 -04:00
|
|
|
// ShiftString removes the first and returns that entry as well as the rest of the list
|
2020-05-09 00:44:20 -04:00
|
|
|
func ShiftString(a []string) (string, []string) {
|
|
|
|
|
if len(a) == 0 {
|
|
|
|
|
return "", a
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return a[0], a[1:]
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-24 09:03:30 -06:00
|
|
|
// StripInlineComments removes bash-style comment from input line
|
|
|
|
|
func StripInlineComments(text string) string {
|
2020-10-10 19:39:40 -04:00
|
|
|
b := []byte(text)
|
2017-04-24 09:03:30 -06:00
|
|
|
re := regexp.MustCompile("(?s)#.*")
|
2020-10-10 19:39:40 -04:00
|
|
|
b = re.ReplaceAll(b, nil)
|
|
|
|
|
return strings.TrimSpace(string(b))
|
2017-04-24 09:03:30 -06:00
|
|
|
}
|
|
|
|
|
|
2020-03-10 23:06:00 -04:00
|
|
|
// SuppressOutput suppresses the output of a function unless there is an error
|
|
|
|
|
func SuppressOutput(f errfunc) error {
|
|
|
|
|
rescueStdout := os.Stdout
|
|
|
|
|
r, w, _ := os.Pipe()
|
|
|
|
|
os.Stdout = w
|
|
|
|
|
|
|
|
|
|
err := f()
|
|
|
|
|
|
|
|
|
|
w.Close()
|
|
|
|
|
out, _ := ioutil.ReadAll(r)
|
|
|
|
|
os.Stdout = rescueStdout
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Printf(string(out[:]))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-24 09:03:30 -06:00
|
|
|
// ToBool returns a bool value for a given string
|
|
|
|
|
func ToBool(s string) bool {
|
|
|
|
|
return s == "true"
|
|
|
|
|
}
|
2017-09-03 19:34:44 -04:00
|
|
|
|
2020-11-01 15:53:53 -05:00
|
|
|
// ToInt returns an int value for a given string
|
|
|
|
|
func ToInt(s string, defaultValue int) int {
|
|
|
|
|
i, err := strconv.Atoi(s)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return defaultValue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return i
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-04 00:48:02 -04:00
|
|
|
// UcFirst uppercases the first character in a string
|
|
|
|
|
func UcFirst(str string) string {
|
|
|
|
|
for i, v := range str {
|
|
|
|
|
return string(unicode.ToUpper(v)) + str[i+1:]
|
|
|
|
|
}
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-21 18:35:09 -05:00
|
|
|
// IsValidAppName verifies that the app name matches naming restrictions
|
2020-03-10 14:14:37 -04:00
|
|
|
func IsValidAppName(appName string) error {
|
2017-09-03 19:34:44 -04:00
|
|
|
if appName == "" {
|
2020-12-21 18:35:09 -05:00
|
|
|
return errors.New("Please specify an app to run the command on")
|
2017-09-03 19:34:44 -04:00
|
|
|
}
|
2020-03-10 14:14:37 -04:00
|
|
|
|
2020-09-09 20:06:53 -04:00
|
|
|
r, _ := regexp.Compile("^[a-z0-9][^/:_A-Z]*$")
|
2020-03-10 14:14:37 -04:00
|
|
|
if r.MatchString(appName) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-09 20:06:53 -04:00
|
|
|
return errors.New("App name must begin with lowercase alphanumeric character, and cannot include uppercase characters, colons, or underscores")
|
2020-03-10 14:14:37 -04:00
|
|
|
}
|
|
|
|
|
|
2020-12-21 18:35:09 -05:00
|
|
|
// isValidAppNameOld verifies that the app name matches the old naming restrictions
|
|
|
|
|
func isValidAppNameOld(appName string) error {
|
|
|
|
|
if appName == "" {
|
|
|
|
|
return errors.New("Please specify an app to run the command on")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r, _ := regexp.Compile("^[a-z0-9][^/:A-Z]*$")
|
|
|
|
|
if r.MatchString(appName) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return errors.New("App name must begin with lowercase alphanumeric character, and cannot include uppercase characters, or colons")
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-03 15:20:46 -05:00
|
|
|
// AppDoesNotExist wraps error to include the app name
|
|
|
|
|
// and is used to distinguish between a normal error and an error
|
|
|
|
|
// where the app is missing
|
2021-02-03 04:29:57 -05:00
|
|
|
type AppDoesNotExist struct {
|
|
|
|
|
appName string
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-03 15:24:00 -05:00
|
|
|
// ExitCode returns an exit code to use in case this error bubbles
|
|
|
|
|
// up into an os.Exit() call
|
2021-02-03 15:20:46 -05:00
|
|
|
func (err *AppDoesNotExist) ExitCode() int {
|
|
|
|
|
return 20
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-03 15:24:00 -05:00
|
|
|
// Error returns a standard non-existent app error
|
2021-02-03 04:29:57 -05:00
|
|
|
func (err *AppDoesNotExist) Error() string {
|
|
|
|
|
return fmt.Sprintf("App %s does not exist", err.appName)
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-21 18:35:09 -05:00
|
|
|
// VerifyAppName checks if an app conforming to either the old or new
|
|
|
|
|
// naming conventions exists
|
2020-03-10 14:14:37 -04:00
|
|
|
func VerifyAppName(appName string) error {
|
2020-12-21 18:35:09 -05:00
|
|
|
newErr := IsValidAppName(appName)
|
|
|
|
|
oldErr := isValidAppNameOld(appName)
|
|
|
|
|
if newErr != nil && oldErr != nil {
|
|
|
|
|
return newErr
|
2017-09-03 19:34:44 -04:00
|
|
|
}
|
2020-03-10 14:14:37 -04:00
|
|
|
|
|
|
|
|
appRoot := AppRoot(appName)
|
|
|
|
|
if !DirectoryExists(appRoot) {
|
2021-02-03 04:29:57 -05:00
|
|
|
return &AppDoesNotExist{appName}
|
2020-03-10 14:14:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
2017-09-03 19:34:44 -04:00
|
|
|
}
|
|
|
|
|
|
2020-02-09 20:37:03 -05:00
|
|
|
func times(str string, n int) (out string) {
|
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
|
out += str
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|