diff --git a/plugins/common/.gitignore b/plugins/common/.gitignore new file mode 100644 index 000000000..a39ded4ae --- /dev/null +++ b/plugins/common/.gitignore @@ -0,0 +1 @@ +/prop diff --git a/plugins/common/Makefile b/plugins/common/Makefile new file mode 100644 index 000000000..918b966ec --- /dev/null +++ b/plugins/common/Makefile @@ -0,0 +1,10 @@ +BUILD = prop +PLUGIN_NAME = common + +clean-prop: + rm -rf prop + +prop: clean-prop **/**/prop.go + go build $(GO_ARGS) -o prop src/prop/prop.go + +include ../../common.mk diff --git a/plugins/common/common.go b/plugins/common/common.go index 3d62fddab..3ad404df2 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -262,6 +262,15 @@ func MustGetEnv(key string) (val string) { return } +// 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 +} + // ReadFirstLine gets the first line of a file that has contents and returns it // if there are no contents, an empty string is returned // will also return an empty string if the file does not exist diff --git a/plugins/common/properties.go b/plugins/common/properties.go index d780003ff..1e6ff8b15 100644 --- a/plugins/common/properties.go +++ b/plugins/common/properties.go @@ -115,7 +115,7 @@ func PropertyGetDefault(pluginName, appName, property, defaultValue string) (val // PropertyListAdd adds a property to a list at an optionally specified index func PropertyListAdd(pluginName string, appName string, property string, value string, index int) error { - if err := PropertyTouch(pluginName, appName, property); err != nil { + if err := propertyTouch(pluginName, appName, property); err != nil { return err } @@ -262,9 +262,45 @@ func PropertyListRemove(pluginName string, appName string, property string, valu return nil } +// PropertyListRemoveByPrefix removes a value by prefix from a property list +func PropertyListRemoveByPrefix(pluginName string, appName string, property string, prefix string) error { + lines, err := PropertyListGet(pluginName, appName, property) + if err != nil { + return err + } + + propertyPath := getPropertyPath(pluginName, appName, property) + file, err := os.OpenFile(propertyPath, os.O_RDWR|os.O_TRUNC, 0600) + if err != nil { + return err + } + + found := false + w := bufio.NewWriter(file) + for _, line := range lines { + if strings.HasPrefix(line, prefix) { + found = true + continue + } + fmt.Fprintln(w, line) + } + if err = w.Flush(); err != nil { + return fmt.Errorf("Unable to write %s config value %s.%s: %s", pluginName, appName, property, err.Error()) + } + + file.Chmod(0600) + setPermissions(propertyPath, 0600) + + if !found { + return errors.New("Property not found, nothing was removed") + } + + return nil +} + // PropertyListSet sets a value within a property list at a specified index func PropertyListSet(pluginName string, appName string, property string, value string, index int) error { - if err := PropertyTouch(pluginName, appName, property); err != nil { + if err := propertyTouch(pluginName, appName, property); err != nil { return err } @@ -310,8 +346,8 @@ func PropertyListSet(pluginName string, appName string, property string, value s return nil } -// PropertyTouch ensures a given application property file exists -func PropertyTouch(pluginName string, appName string, property string) error { +// propertyTouch ensures a given application property file exists +func propertyTouch(pluginName string, appName string, property string) error { if err := makePluginAppPropertyPath(pluginName, appName); err != nil { return fmt.Errorf("Unable to create %s config directory for %s: %s", pluginName, appName, err.Error()) } @@ -332,7 +368,7 @@ func PropertyTouch(pluginName string, appName string, property string) error { // PropertyWrite writes a value for a given application property func PropertyWrite(pluginName string, appName string, property string, value string) error { - if err := PropertyTouch(pluginName, appName, property); err != nil { + if err := propertyTouch(pluginName, appName, property); err != nil { return err } @@ -350,10 +386,13 @@ func PropertyWrite(pluginName string, appName string, property string, value str } // PropertySetup creates the plugin config root -func PropertySetup(pluginName string) (err error) { +func PropertySetup(pluginName string) error { pluginConfigRoot := getPluginConfigPath(pluginName) - if err = os.MkdirAll(pluginConfigRoot, 0755); err != nil { - return + if err := os.MkdirAll(pluginConfigRoot, 0755); err != nil { + return err + } + if err := setPermissions(path.Join(MustGetEnv("DOKKU_LIB_ROOT"), "config"), 0755); err != nil { + return err } return setPermissions(pluginConfigRoot, 0755) } @@ -374,46 +413,40 @@ func getPluginConfigPath(pluginName string) string { } // makePluginAppPropertyPath ensures that a property path exists -func makePluginAppPropertyPath(pluginName string, appName string) (err error) { +func makePluginAppPropertyPath(pluginName string, appName string) error { pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName) - if err = os.MkdirAll(pluginAppConfigRoot, 0755); err != nil { - return + if err := os.MkdirAll(pluginAppConfigRoot, 0755); err != nil { + return err } return setPermissions(pluginAppConfigRoot, 0755) } // setPermissions sets the proper owner and filemode for a given file -func setPermissions(path string, fileMode os.FileMode) (err error) { - if err = os.Chmod(path, fileMode); err != nil { +func setPermissions(path string, fileMode os.FileMode) error { + if err := os.Chmod(path, fileMode); err != nil { return err } - systemGroup := os.Getenv("DOKKU_SYSTEM_GROUP") - systemUser := os.Getenv("DOKKU_SYSTEM_USER") - if systemGroup == "" { - systemGroup = "dokku" - } - if systemUser == "" { - systemUser = "dokku" - } + systemGroup := GetenvWithDefault("DOKKU_SYSTEM_GROUP", "dokku") + systemUser := GetenvWithDefault("DOKKU_SYSTEM_USER", "dokku") group, err := user.LookupGroup(systemGroup) if err != nil { - return + return err } user, err := user.Lookup(systemUser) if err != nil { - return + return err } uid, err := strconv.Atoi(user.Uid) if err != nil { - return + return err } gid, err := strconv.Atoi(group.Gid) if err != nil { - return + return err } return os.Chown(path, uid, gid) } diff --git a/plugins/common/property-functions b/plugins/common/property-functions index 394bc0d9e..66eeb67a4 100755 --- a/plugins/common/property-functions +++ b/plugins/common/property-functions @@ -2,102 +2,93 @@ set -eo pipefail [[ $DOKKU_TRACE ]] && set -x -fn-plugin-property-delete() { - declare desc="delete a key from the property store for an app" - declare PLUGIN="$1" APP="$2" KEY="$3" +fn-plugin-property-get() { + declare desc="returns the value for a given property" + declare PLUGIN="$1" APP="$2" KEY="$3" DEFAULT="$4" - if [[ -z "$PLUGIN" ]] || [[ -z "$APP" ]] || [[ -z "$KEY" ]]; then - return 1 - fi - - rm -f "${DOKKU_LIB_ROOT}/config/${PLUGIN}/${APP}/${KEY}" + fn-plugin-property-get-default "$PLUGIN" "$APP" "$KEY" "$DEFAULT" } fn-plugin-property-destroy() { declare desc="destroy the properties for an app" declare PLUGIN="$1" APP="$2" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "destroy" "$PLUGIN" "$APP" "$KEY" +} - if [[ -z "$PLUGIN" ]] || [[ -z "$APP" ]]; then - return 1 - fi - - if [[ "$APP" == "_all_" ]]; then - rm -rf "${DOKKU_LIB_ROOT}/config/${PLUGIN}" - else - rm -rf "${DOKKU_LIB_ROOT}/config/${PLUGIN}/${APP}" - fi +fn-plugin-property-delete() { + declare desc="delete a key from the property store for an app" + declare PLUGIN="$1" APP="$2" KEY="$3" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "del" "$PLUGIN" "$APP" "$KEY" } fn-plugin-property-exists() { declare desc="returns whether the property store has a value for an app" - declare PLUGIN="$1" APP="$2" KEY="$3" DEFAULT="$4" - local CONFIG_VALUE - - if [[ -z "$PLUGIN" ]] || [[ -z "$APP" ]] || [[ -z "$KEY" ]]; then - return 1 - fi - - if [[ ! -f "${DOKKU_LIB_ROOT:?}/config/${PLUGIN}/${APP}/${KEY}" ]]; then - return 1 - fi -} - -fn-plugin-property-get() { - declare desc="returns the property store value for an app" - declare PLUGIN="$1" APP="$2" KEY="$3" DEFAULT="$4" - local CONFIG_VALUE - - if [[ -z "$PLUGIN" ]] || [[ -z "$APP" ]] || [[ -z "$KEY" ]]; then - return 1 - fi - - CONFIG_VALUE="$(fn-plugin-property-read "$PLUGIN" "$APP" "$KEY" || true)" - if [[ -z "$CONFIG_VALUE" ]]; then - CONFIG_VALUE="$DEFAULT" - fi - echo "$CONFIG_VALUE" -} - -fn-plugin-property-setup() { - declare desc="creates the plugin config root" - declare PLUGIN="$1" - - if [[ -z "$PLUGIN" ]]; then - return 1 - fi - - mkdir -p "${DOKKU_LIB_ROOT}/config/${PLUGIN}" - chmod 0755 "${DOKKU_LIB_ROOT}/config/${PLUGIN}" - - local dokku_system_group=${DOKKU_SYSTEM_GROUP:="dokku"} - local dokku_system_user=${DOKKU_SYSTEM_USER:="dokku"} - chown "${dokku_system_user}:${dokku_system_group}" "${DOKKU_LIB_ROOT}/config/${PLUGIN}" -} - -fn-plugin-property-read() { - declare desc="read a key from the property store for an app" declare PLUGIN="$1" APP="$2" KEY="$3" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "exists" "$PLUGIN" "$APP" "$KEY" +} - if [[ -z "$PLUGIN" ]] || [[ -z "$APP" ]] || [[ -z "$KEY" ]]; then - return 1 - fi +fn-plugin-property-get-all() { + declare desc="returns a map of all properties for a given app" + declare PLUGIN="$1" APP="$2" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "get-all" "$PLUGIN" "$APP" +} - if [[ ! -f "${DOKKU_LIB_ROOT:?}/config/${PLUGIN}/${APP}/${KEY}" ]]; then - return 1 - fi +fn-plugin-property-get-default() { + declare desc="returns the value for a given property with a specified default value" + declare PLUGIN="$1" APP="$2" KEY="$3" DEFAULT="$4" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "get-with-default" "$PLUGIN" "$APP" "$KEY" "$DEFAULT" +} - cat "${DOKKU_LIB_ROOT}/config/${PLUGIN}/${APP}/${KEY}" +fn-plugin-property-list-add() { + declare desc="adds a property to a list at an optionally specified index" + declare PLUGIN="$1" APP="$2" KEY="$3" VALUE="$4" INDEX="$5" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "rpush" "$PLUGIN" "$APP" "$KEY" "$VALUE" "$INDEX" +} + +fn-plugin-property-list-get() { + declare desc="returns a property list" + declare PLUGIN="$1" APP="$2" KEY="$3" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "lrange" "$PLUGIN" "$APP" "$KEY" +} + +fn-plugin-property-list-get-by-index() { + declare desc="returns an entry within property list by index" + declare PLUGIN="$1" APP="$2" KEY="$3" INDEX="$4" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "lindex" "$PLUGIN" "$APP" "$KEY" "$INDEX" +} + +fn-plugin-property-list-get-by-value() { + declare desc="returns an entry within property list by value" + declare PLUGIN="$1" APP="$2" KEY="$3" VALUE="$4" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "lismember" "$PLUGIN" "$APP" "$KEY" "$VALUE" +} + +fn-plugin-property-list-remove() { + declare desc="removes a value from a property list" + declare PLUGIN="$1" APP="$2" KEY="$3" VALUE="$4" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "lrem" "$PLUGIN" "$APP" "$KEY" "$VALUE" +} + +fn-plugin-property-list-remove-by-prefix() { + declare desc="removes a value from a property list" + declare PLUGIN="$1" APP="$2" KEY="$3" PREFIX="$4" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "lrem-by-prefix" "$PLUGIN" "$APP" "$KEY" "$PREFIX" +} + +fn-plugin-property-list-set() { + declare desc="sets a value within a property list at a specified index" + declare PLUGIN="$1" APP="$2" KEY="$3" VALUE="$4" INDEX="$5" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "lset" "$PLUGIN" "$APP" "$KEY" "$VALUE" "$INDEX" } fn-plugin-property-write() { declare desc="read a key from the property store for an app" declare PLUGIN="$1" APP="$2" KEY="$3" VALUE="$4" - - if [[ -z "$PLUGIN" ]] || [[ -z "$APP" ]] || [[ -z "$KEY" ]]; then - return 1 - fi - - mkdir -p "${DOKKU_LIB_ROOT}/config/${PLUGIN}/${APP}" - echo "$VALUE" >"${DOKKU_LIB_ROOT}/config/${PLUGIN}/${APP}/${KEY}" - chmod 600 "${DOKKU_LIB_ROOT}/config/${PLUGIN}/${APP}/${KEY}" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "set" "$PLUGIN" "$APP" "$KEY" "$VALUE" +} + +fn-plugin-property-setup() { + declare desc="creates the plugin config root" + declare PLUGIN="$1" + "$PLUGIN_CORE_AVAILABLE_PATH/common/prop" "setup" "$PLUGIN" } diff --git a/plugins/common/src/prop/prop.go b/plugins/common/src/prop/prop.go new file mode 100644 index 000000000..0c4a1bc99 --- /dev/null +++ b/plugins/common/src/prop/prop.go @@ -0,0 +1,181 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strconv" + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +func strToInt(s string, defaultValue int, allowEmpty bool) int { + if s == "" { + if !allowEmpty { + os.Exit(1) + } + return defaultValue + } + + index, err := strconv.Atoi(s) + if err != nil { + os.Exit(1) + } + + return index +} + +func main() { + flag.Parse() + + cmd := flag.Arg(0) + pluginName := flag.Arg(1) + + switch cmd { + case "rpush": + appName := flag.Arg(2) + property := flag.Arg(3) + value := flag.Arg(4) + index := strToInt(flag.Arg(5), 0, true) + err := common.PropertyListAdd(pluginName, appName, property, value, index) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + case "llen": + appName := flag.Arg(2) + property := flag.Arg(3) + lines, err := common.PropertyListGet(pluginName, appName, property) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + println(len(lines)) + case "lrange": + appName := flag.Arg(2) + property := flag.Arg(3) + lines, err := common.PropertyListGet(pluginName, appName, property) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + for _, line := range lines { + println(line) + } + case "lindex": + appName := flag.Arg(2) + property := flag.Arg(3) + index := strToInt(flag.Arg(4), 0, false) + propertyValue, err := common.PropertyListGetByIndex(pluginName, appName, property, index) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + println(propertyValue) + case "lismember": + appName := flag.Arg(2) + property := flag.Arg(3) + value := flag.Arg(4) + _, err := common.PropertyListGetByValue(pluginName, appName, property, value) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + case "lrem": + appName := flag.Arg(2) + property := flag.Arg(3) + value := flag.Arg(4) + err := common.PropertyListRemove(pluginName, appName, property, value) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + case "lrem-by-prefix": + appName := flag.Arg(2) + property := flag.Arg(3) + prefix := flag.Arg(4) + err := common.PropertyListRemoveByPrefix(pluginName, appName, property, prefix) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + case "lset": + appName := flag.Arg(2) + property := flag.Arg(3) + value := flag.Arg(4) + index := strToInt(flag.Arg(5), 0, false) + err := common.PropertyListSet(pluginName, appName, property, value, index) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + case "set": + appName := flag.Arg(2) + property := flag.Arg(3) + value := flag.Arg(4) + err := common.PropertyWrite(pluginName, appName, property, value) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + case "get": + appName := flag.Arg(2) + property := flag.Arg(3) + value := common.PropertyGet(pluginName, appName, property) + if value != "" { + println(value) + } + case "get-with-default": + appName := flag.Arg(2) + property := flag.Arg(3) + defaultValue := flag.Arg(4) + value := common.PropertyGetDefault(pluginName, appName, property, defaultValue) + if value != "" { + println(value) + } + case "get-all": + appName := flag.Arg(2) + values, err := common.PropertyGetAll(pluginName, appName) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + for key, value := range values { + fmt.Fprintln(os.Stdout, key, strings.TrimSuffix(value, "\n")) + } + case "exists": + appName := flag.Arg(2) + property := flag.Arg(3) + exists := common.PropertyExists(pluginName, appName, property) + if !exists { + os.Exit(1) + } + case "del": + appName := flag.Arg(2) + property := flag.Arg(3) + err := common.PropertyDelete(pluginName, appName, property) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + case "setup": + err := common.PropertySetup(pluginName) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + case "destroy": + appName := flag.Arg(2) + err := common.PropertyDestroy(pluginName, appName) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + } +}