diff --git a/plugins/common/data.go b/plugins/common/data.go index 06d2ecda60e..a3c1e8092e4 100644 --- a/plugins/common/data.go +++ b/plugins/common/data.go @@ -13,11 +13,10 @@ func CreateAppDataDirectory(pluginName, appName string) error { return err } - if err := SetPermissions(directory, 0755); err != nil { - return err - } - - return nil + return SetPermissions(SetPermissionInput{ + Filename: directory, + Mode: os.FileMode(0755), + }) } // CreateDataDirectory creates a data directory for the given plugin/app combination with the correct permissions @@ -27,11 +26,10 @@ func CreateDataDirectory(pluginName string) error { return err } - if err := SetPermissions(directory, 0755); err != nil { - return err - } - - return nil + return SetPermissions(SetPermissionInput{ + Filename: directory, + Mode: os.FileMode(0755), + }) } // GetAppDataDirectory returns the path to the data directory for the given plugin/app combination diff --git a/plugins/common/io.go b/plugins/common/io.go index 7a147d356ae..4df5e9aa99a 100644 --- a/plugins/common/io.go +++ b/plugins/common/io.go @@ -151,24 +151,33 @@ func ReadFirstLine(filename string) (text string) { return } +// SetPermissionsInput is the input struct for SetPermissions +type SetPermissionInput struct { + Filename string + GroupName string + Mode os.FileMode + Username string +} + // SetPermissions sets the proper owner and filemode for a given file -func SetPermissions(path string, fileMode os.FileMode) error { - if err := os.Chmod(path, fileMode); err != nil { +func SetPermissions(input SetPermissionInput) error { + if err := os.Chmod(input.Filename, input.Mode); err != nil { return err } - systemGroup := GetenvWithDefault("DOKKU_SYSTEM_GROUP", "dokku") - systemUser := GetenvWithDefault("DOKKU_SYSTEM_USER", "dokku") - if strings.HasPrefix(path, "/etc/sudoers.d/") { - systemGroup = "root" - systemUser = "root" + if input.GroupName == "" { + input.GroupName = GetenvWithDefault("DOKKU_SYSTEM_GROUP", "dokku") + } + + if input.Username == "" { + input.Username = GetenvWithDefault("DOKKU_SYSTEM_USER", "dokku") } - group, err := user.LookupGroup(systemGroup) + group, err := user.LookupGroup(input.GroupName) if err != nil { return err } - user, err := user.Lookup(systemUser) + user, err := user.Lookup(input.Username) if err != nil { return err } @@ -182,7 +191,7 @@ func SetPermissions(path string, fileMode os.FileMode) error { if err != nil { return err } - return os.Chown(path, uid, gid) + return os.Chown(input.Filename, uid, gid) } // TouchFile creates an empty file at the specified path @@ -198,16 +207,18 @@ func TouchFile(filename string) error { return err } - if err := SetPermissions(filename, mode); err != nil { - return err - } - return nil + return SetPermissions(SetPermissionInput{ + Filename: filename, + Mode: mode, + }) } type WriteSliceToFileInput struct { - Filename string - Lines []string - Mode os.FileMode + Filename string + GroupName string + Lines []string + Mode os.FileMode + Username string } // WriteSliceToFile writes a slice of strings to a file @@ -230,9 +241,17 @@ func WriteSliceToFile(input WriteSliceToFileInput) error { return err } - if err := SetPermissions(input.Filename, input.Mode); err != nil { - return err + permissionsInput := SetPermissionInput{ + Filename: input.Filename, + Mode: input.Mode, } - return nil + if input.GroupName != "" { + permissionsInput.GroupName = input.GroupName + } + if input.Username != "" { + permissionsInput.Username = input.Username + } + + return SetPermissions(permissionsInput) } diff --git a/plugins/common/properties.go b/plugins/common/properties.go index 0935b773da8..48d4aa49194 100644 --- a/plugins/common/properties.go +++ b/plugins/common/properties.go @@ -202,7 +202,10 @@ func PropertyListWrite(pluginName string, appName string, property string, value } file.Chmod(0600) - SetPermissions(propertyPath, 0600) + SetPermissions(SetPermissionInput{ + Filename: propertyPath, + Mode: os.FileMode(0600), + }) return nil } @@ -329,7 +332,10 @@ func PropertyListRemove(pluginName string, appName string, property string, valu } file.Chmod(0600) - SetPermissions(propertyPath, 0600) + SetPermissions(SetPermissionInput{ + Filename: propertyPath, + Mode: os.FileMode(0600), + }) if !found { return errors.New("Property not found, nothing was removed") @@ -365,7 +371,10 @@ func PropertyListRemoveByPrefix(pluginName string, appName string, property stri } file.Chmod(0600) - SetPermissions(propertyPath, 0600) + SetPermissions(SetPermissionInput{ + Filename: propertyPath, + Mode: os.FileMode(0600), + }) if !found { return errors.New("Property not found, nothing was removed") @@ -441,7 +450,10 @@ func PropertyWrite(pluginName string, appName string, property string, value str fmt.Fprint(file, value) file.Chmod(0600) - SetPermissions(propertyPath, 0600) + SetPermissions(SetPermissionInput{ + Filename: propertyPath, + Mode: os.FileMode(0600), + }) return nil } @@ -451,10 +463,19 @@ func PropertySetup(pluginName string) error { if err := os.MkdirAll(pluginConfigRoot, 0755); err != nil { return err } - if err := SetPermissions(filepath.Join(MustGetEnv("DOKKU_LIB_ROOT"), "config"), 0755); err != nil { + + input := SetPermissionInput{ + Filename: filepath.Join(MustGetEnv("DOKKU_LIB_ROOT"), "config"), + Mode: os.FileMode(0755), + } + + if err := SetPermissions(input); err != nil { return err } - return SetPermissions(pluginConfigRoot, 0755) + return SetPermissions(SetPermissionInput{ + Filename: pluginConfigRoot, + Mode: os.FileMode(0755), + }) } func getPropertyPath(pluginName string, appName string, property string) string { @@ -478,5 +499,8 @@ func makePluginAppPropertyPath(pluginName string, appName string) error { if err := os.MkdirAll(pluginAppConfigRoot, 0755); err != nil { return err } - return SetPermissions(pluginAppConfigRoot, 0755) + return SetPermissions(SetPermissionInput{ + Filename: pluginAppConfigRoot, + Mode: os.FileMode(0755), + }) } diff --git a/plugins/config/config.go b/plugins/config/config.go index 1504c35651a..3bfbf59d6da 100644 --- a/plugins/config/config.go +++ b/plugins/config/config.go @@ -8,7 +8,7 @@ import ( "github.com/dokku/dokku/plugins/common" ) -//Get retrieves a value from a config. If appName is empty the global config is used. +// Get retrieves a value from a config. If appName is empty the global config is used. func Get(appName string, key string) (value string, ok bool) { env, err := loadAppOrGlobalEnv(appName) if err != nil { @@ -20,7 +20,7 @@ func Get(appName string, key string) (value string, ok bool) { return env.Get(key) } -//GetWithDefault gets a value from a config. If appName is empty the global config is used. If the appName or key do not exist defaultValue is returned. +// GetWithDefault gets a value from a config. If appName is empty the global config is used. If the appName or key do not exist defaultValue is returned. func GetWithDefault(appName string, key string, defaultValue string) (value string) { value, ok := Get(appName, key) if !ok { @@ -29,7 +29,7 @@ func GetWithDefault(appName string, key string, defaultValue string) (value stri return value } -//SetMany variables in the environment. If appName is empty the global config is used. If restart is true the app is restarted. +// SetMany variables in the environment. If appName is empty the global config is used. If restart is true the app is restarted. func SetMany(appName string, entries map[string]string, restart bool) (err error) { global := appName == "" || appName == "--global" env, err := loadAppOrGlobalEnv(appName) @@ -52,7 +52,10 @@ func SetMany(appName string, entries map[string]string, restart bool) (err error fmt.Println(prettyPrintEnvEntries(" ", entries)) } env.Write() - common.SetPermissions(env.Filename(), 0600) + common.SetPermissions(common.SetPermissionInput{ + Filename: env.Filename(), + Mode: os.FileMode(0600), + }) triggerUpdate(appName, "set", keys) } if !global && restart && env.GetBoolDefault("DOKKU_APP_RESTORE", true) { @@ -61,7 +64,7 @@ func SetMany(appName string, entries map[string]string, restart bool) (err error return } -//UnsetMany a value in a config. If appName is empty the global config is used. If restart is true the app is restarted. +// UnsetMany a value in a config. If appName is empty the global config is used. If restart is true the app is restarted. func UnsetMany(appName string, keys []string, restart bool) (err error) { global := appName == "" || appName == "--global" env, err := loadAppOrGlobalEnv(appName) @@ -85,7 +88,10 @@ func UnsetMany(appName string, keys []string, restart bool) (err error) { } if changed { env.Write() - common.SetPermissions(env.Filename(), 0600) + common.SetPermissions(common.SetPermissionInput{ + Filename: env.Filename(), + Mode: os.FileMode(0600), + }) triggerUpdate(appName, "unset", keys) } if !global && restart && env.GetBoolDefault("DOKKU_APP_RESTORE", true) { @@ -94,7 +100,7 @@ func UnsetMany(appName string, keys []string, restart bool) (err error) { return } -//UnsetAll removes all config keys +// UnsetAll removes all config keys func UnsetAll(appName string, restart bool) (err error) { global := appName == "" || appName == "--global" env, err := loadAppOrGlobalEnv(appName) @@ -109,7 +115,10 @@ func UnsetAll(appName string, restart bool) (err error) { } if changed { env.Write() - common.SetPermissions(env.Filename(), 0600) + common.SetPermissions(common.SetPermissionInput{ + Filename: env.Filename(), + Mode: os.FileMode(0600), + }) triggerUpdate(appName, "clear", []string{}) } if !global && restart && env.GetBoolDefault("DOKKU_APP_RESTORE", true) { @@ -132,7 +141,7 @@ func triggerUpdate(appName string, operation string, args []string) { } } -//getEnvironment for the given app (global config if appName is empty). Merge with global environment if merged is true. +// getEnvironment for the given app (global config if appName is empty). Merge with global environment if merged is true. func getEnvironment(appName string, merged bool) (env *Env) { var err error if appName != "" && merged { diff --git a/plugins/logs/triggers.go b/plugins/logs/triggers.go index fe95f2e9ba7..45783981636 100644 --- a/plugins/logs/triggers.go +++ b/plugins/logs/triggers.go @@ -84,11 +84,10 @@ func TriggerInstall() error { return err } - if err := common.SetPermissions(logDirectory, 0755); err != nil { - return err - } - - return nil + return common.SetPermissions(common.SetPermissionInput{ + Filename: logDirectory, + Mode: os.FileMode(0755), + }) } // TriggerLogsGetProperty writes the logs key to stdout for a given app container diff --git a/plugins/scheduler-k3s/functions.go b/plugins/scheduler-k3s/functions.go index d71842a597d..2deb4aa4a80 100644 --- a/plugins/scheduler-k3s/functions.go +++ b/plugins/scheduler-k3s/functions.go @@ -14,6 +14,8 @@ import ( appjson "github.com/dokku/dokku/plugins/app-json" "github.com/dokku/dokku/plugins/common" + resty "github.com/go-resty/resty/v2" + "golang.org/x/sync/errgroup" "gopkg.in/yaml.v3" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" @@ -722,6 +724,61 @@ func installHelmCharts(ctx context.Context, clientset KubernetesClient) error { return nil } +func installHelperCommands(ctx context.Context) error { + urls := map[string]string{ + "kubectx": "https://github.com/ahmetb/kubectx/releases/latest/download/kubectx", + "kubens": "https://github.com/ahmetb/kubectx/releases/latest/download/kubens", + } + + client := resty.New() + for binaryName, url := range urls { + resp, err := client.R(). + SetContext(ctx). + Get(url) + if err != nil { + return fmt.Errorf("Unable to download %s: %w", binaryName, err) + } + if resp == nil { + return fmt.Errorf("Missing response from %s download: %w", binaryName, err) + } + + if resp.StatusCode() != 200 { + return fmt.Errorf("Invalid status code for %s: %d", binaryName, resp.StatusCode()) + } + + f, err := os.Create(filepath.Join("/usr/local/bin", binaryName)) + if err != nil { + return fmt.Errorf("Unable to create %s: %w", binaryName, err) + } + + if err := f.Close(); err != nil { + return fmt.Errorf("Unable to close %s file: %w", binaryName, err) + } + + err = common.WriteSliceToFile(common.WriteSliceToFileInput{ + Filename: f.Name(), + GroupName: "root", + Lines: strings.Split(resp.String(), "\n"), + Mode: os.FileMode(0755), + Username: "root", + }) + if err != nil { + return fmt.Errorf("Unable to write %s to file: %w", binaryName, err) + } + + fi, err := os.Stat(f.Name()) + if err != nil { + return fmt.Errorf("Unable to get %s file size: %w", binaryName, err) + } + + if fi.Size() == 0 { + return fmt.Errorf("Invalid %s filesize", binaryName) + } + } + + return nil +} + func isK3sInstalled() error { if !common.FileExists("/usr/local/bin/k3s") { return fmt.Errorf("k3s binary is not available") @@ -797,6 +854,17 @@ func kubernetesNodeToNode(node v1.Node) Node { } } +func uninstallHelperCommands(ctx context.Context) error { + errs, _ := errgroup.WithContext(ctx) + errs.Go(func() error { + return os.RemoveAll("/usr/local/bin/kubectx") + }) + errs.Go(func() error { + return os.RemoveAll("/usr/local/bin/kubens") + }) + return errs.Wait() +} + func waitForPodBySelectorRunning(ctx context.Context, input WaitForPodBySelectorRunningInput) error { pods, err := waitForPodToExist(ctx, WaitForPodToExistInput{ Clientset: input.Clientset, diff --git a/plugins/scheduler-k3s/subcommands.go b/plugins/scheduler-k3s/subcommands.go index 2573383234e..a2d692f21ba 100644 --- a/plugins/scheduler-k3s/subcommands.go +++ b/plugins/scheduler-k3s/subcommands.go @@ -84,6 +84,7 @@ func CommandInitialize(serverIP string, taintScheduling bool) error { common.LogInfo2Quiet("Downloading k3s installer") client := resty.New() resp, err := client.R(). + SetContext(ctx). Get("https://get.k3s.io") if err != nil { return fmt.Errorf("Unable to download k3s installer: %w", err) @@ -247,6 +248,12 @@ func CommandInitialize(serverIP string, taintScheduling bool) error { return fmt.Errorf("Unable to install helm charts: %w", err) } + common.LogInfo2Quiet("Installing helper commands") + err = installHelperCommands(ctx) + if err != nil { + return fmt.Errorf("Unable to install helper commands: %w", err) + } + common.LogVerboseQuiet("Done") return nil @@ -709,5 +716,6 @@ func CommandUninstall() error { return fmt.Errorf("Invalid exit code from k3s uninstaller command: %d", uninstallerCmd.ExitCode) } - return nil + common.LogInfo2Quiet("Removing k3s dependencies") + return uninstallHelperCommands(context.Background()) }