Merge pull request #6546 from dokku/kubens-kubectx
Install kubectx and kubens helper binaries when setting up a k3s cluster
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
group, err := user.LookupGroup(systemGroup)
|
||||
if input.Username == "" {
|
||||
input.Username = GetenvWithDefault("DOKKU_SYSTEM_USER", "dokku")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user