Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Install cmd #478

Merged
merged 3 commits into from
Dec 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ bindata
.*.stamp
.tmp
.terraform
.idea
*.tfstate*
aws_private.pem
out.json
Expand Down
4 changes: 3 additions & 1 deletion cmd/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ func ConfigFromYaml(cfgPath string) (*config.ClusterConfig, error) {
if clusterConfig.Spec.Storage.Type == config.KineStorageType && clusterConfig.Spec.Storage.Kine == nil {
clusterConfig.Spec.Storage.Kine = config.DefaultKineConfig(k0sVars.DataDir)
}

if clusterConfig.Install == nil {
clusterConfig.Install = config.DefaultInstallSpec()
}
return clusterConfig, nil
}
83 changes: 83 additions & 0 deletions cmd/install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package cmd

import (
"fmt"
"os"
"reflect"
"strings"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/k0sproject/k0s/pkg/apis/v1beta1"
"github.com/k0sproject/k0s/pkg/install"
)

func init() {
installCmd.Flags().StringVar(&role, "role", "server", "node role (possible values: server or worker. In a single-node setup, a worker role should be used)")
}

var (
role string

installCmd = &cobra.Command{
Use: "install",
Short: "Helper command for setting up k0s on a brand-new system. Must be run as root (or with sudo)",
RunE: func(cmd *cobra.Command, args []string) error {
switch role {
case "server", "worker":
return setup()
default:
logrus.Errorf("invalid value %s for install role", role)
return cmd.Help()
}
},
}
)

// the setup functions:
// * Ensures that the proper users are created
// * sets up startup and logging for k0s
func setup() error {
if os.Geteuid() != 0 {
logrus.Fatal("this command must be run as root!")
}

if role == "server" {
if err := createServerUsers(); err != nil {
logrus.Errorf("failed to create server users: %v", err)
}
}

return nil
}

func createServerUsers() error {
clusterConfig, err := ConfigFromYaml(cfgFile)
if err != nil {
return err
}
users := getUserList(*clusterConfig.Install.SystemUsers)
trawler marked this conversation as resolved.
Show resolved Hide resolved

var messages []string
for _, v := range users {
if err := install.EnsureUser(v, k0sVars.DataDir); err != nil {
messages = append(messages, err.Error())
}
}

if len(messages) > 0 {
return fmt.Errorf(strings.Join(messages, "\n"))
}
return nil
}

func getUserList(sysUsers v1beta1.SystemUser) []string {
v := reflect.ValueOf(sysUsers)
values := make([]string, v.NumField())

for i := 0; i < v.NumField(); i++ {
values[i] = v.Field(i).String()
}
return values
}
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func init() {
rootCmd.AddCommand(etcdCmd)
rootCmd.AddCommand(docs)
rootCmd.AddCommand(userCmd)
rootCmd.AddCommand(installCmd)

longDesc = "k0s - The zero friction Kubernetes - https://k0sproject.io"
if build.EulaNotice != "" {
Expand Down
2 changes: 1 addition & 1 deletion cmd/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ users:
Use: "token",
Short: "Manage join tokens",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
return tokenCreateCmd.Usage()
},
}

Expand Down
5 changes: 3 additions & 2 deletions cmd/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ users:

// userCmd creates new certs and kubeConfig for a user
userCmd = &cobra.Command{
Use: "user",
Use: "user [command]",
trawler marked this conversation as resolved.
Show resolved Hide resolved
Short: "Manage user access",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
// user command does nothing
return userCreateCmd.Usage()
},
}

Expand Down
15 changes: 13 additions & 2 deletions internal/util/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ package util
import (
"fmt"
"os"
"os/exec"
)

// FileExists checks if a file exists and is not a directory before we
// try using it to prevent further errors.
func FileExists(filename string) bool {
info, err := os.Stat(filename)
func FileExists(fileName string) bool {
info, err := os.Stat(fileName)
if os.IsNotExist(err) {
return false
}
Expand All @@ -42,3 +43,13 @@ func CheckPathPermissions(path string, perm os.FileMode) error {
}
return nil
}

// Find the path for a given file (similar to `which`)
func GetExecPath(fileName string) (*string, error) {
path, err := exec.LookPath(fileName)
if err != nil {
return nil, err
}

return &path, nil
}
23 changes: 23 additions & 0 deletions internal/util/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,26 @@ func GetGID(name string) (int, error) {
}
return strconv.Atoi(entry.Gid)
}

func CheckIfUserExists(name string) (bool, error) {
_, err := user.Lookup(name)
if _, ok := err.(user.UnknownUserError); ok {
return false, nil
}
if err != nil {
return false, err
}
return true, nil
}

/*
func GetLinuxDist() (string, error) {
if runtime.GOOS == "windows" {
return "", fmt.Errorf("unsupported OS")
}
cfg, err := ini.Load("/etc/os-release")
if err != nil {
fmt.Printf("failed to read file: %v", err)
}
return cfg.Section("").Key("ID").String(), nil
}*/
jasmingacic marked this conversation as resolved.
Show resolved Hide resolved
9 changes: 9 additions & 0 deletions inttest/common/footloosesuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,20 @@ func (s *FootlooseSuite) InitMainController(cfgPath string, dataDir string) erro
defer ssh.Disconnect()

var startCmd string
var installCmd string

if dataDir != "" {
installCmd = fmt.Sprintf("ETCD_UNSUPPORTED_ARCH=arm64 k0s --debug install --data-dir=%s --config=%s", dataDir, cfgPath)
startCmd = fmt.Sprintf("ETCD_UNSUPPORTED_ARCH=arm64 nohup k0s --debug server --data-dir=%s --config=%s >/tmp/k0s-server.log 2>&1 &", dataDir, cfgPath)
} else {
installCmd = fmt.Sprintf("ETCD_UNSUPPORTED_ARCH=arm64 k0s --debug install --config=%s", cfgPath)
startCmd = fmt.Sprintf("ETCD_UNSUPPORTED_ARCH=arm64 nohup k0s --debug server --config=%s >/tmp/k0s-server.log 2>&1 &", cfgPath)
}
_, err = ssh.ExecWithOutput(installCmd)
if err != nil {
return err
}

_, err = ssh.ExecWithOutput(startCmd)
if err != nil {
return err
Expand Down
4 changes: 0 additions & 4 deletions inttest/footloose-alpine/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ RUN echo "#!/bin/sh" > /etc/local.d/machine-id.start \
&& echo "fi" >> /etc/local.d/machine-id.start \
&& chmod +x /etc/local.d/machine-id.start

RUN for i in etcd kube-apiserver kube-controller-manager kube-scheduler konnectivity-server; do \
adduser -H -S -s /sbin/nologin $i; \
done

# Put kubectl into place to ease up debugging
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.19.0/bin/linux/amd64/kubectl \
&& chmod +x ./kubectl \
Expand Down
11 changes: 9 additions & 2 deletions pkg/apis/v1beta1/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ import (
// ClusterConfig cluster manifest
type ClusterConfig struct {
APIVersion string `yaml:"apiVersion" validate:"eq=k0s.k0sproject.io/v1beta1"`
Extensions *ClusterExtensions `yaml:"extensions,omitempty"`
Images *ClusterImages `yaml:"images"`
Install *InstallSpec `yaml:"installConfig,omitempty"`
Kind string `yaml:"kind" validate:"eq=Cluster"`
Metadata *ClusterMeta `yaml:"metadata"`
Spec *ClusterSpec `yaml:"spec"`
Extensions *ClusterExtensions `yaml:"extensions"`
Images *ClusterImages `yaml:"images"`
Telemetry *ClusterTelemetry `yaml:"telemetry"`
}

Expand Down Expand Up @@ -69,6 +70,11 @@ type SchedulerSpec struct {
ExtraArgs map[string]string `yaml:"extraArgs"`
}

// InstallSpec defines the required fields for the `k0s install` command
type InstallSpec struct {
SystemUsers *SystemUser `yaml:"users,omitempty"`
}

// Validate validates cluster config
func (c *ClusterConfig) Validate() []error {
var errors []error
Expand Down Expand Up @@ -118,6 +124,7 @@ func DefaultClusterConfig() *ClusterConfig {
Metadata: &ClusterMeta{
Name: "k0s",
},
Install: DefaultInstallSpec(),
Spec: DefaultClusterSpec(),
Images: DefaultClusterImages(),
Telemetry: DefaultClusterTelemetry(),
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/v1beta1/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type ClusterImages struct {

Calico CalicoImageSpec `yaml:"calico"`

Repository string `yaml:"repository"`
Repository string `yaml:"repository,omitempty"`
}

func (ci *ClusterImages) UnmarshalYAML(unmarshal func(interface{}) error) error {
Expand Down
32 changes: 32 additions & 0 deletions pkg/apis/v1beta1/system.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package v1beta1

import "github.com/k0sproject/k0s/pkg/constant"

// SystemUser defines the user to use for each component
type SystemUser struct {
ControllerManager string `yaml:"kubeControllerUser,omitempty"`
Etcd string `yaml:"etcdUser,omitempty"`
Kine string `yaml:"kineUser,omitempty"`
Konnectivity string `yaml:"konnectivityUser,omitempty"`
KubeAPIServer string `yaml:"kubeAPIserverUser,omitempty"`
KubeScheduler string `yaml:"kubeSchedulerUser,omitempty"`
}

// DefaultSystemUsers returns the default system users to be used for the different components
func DefaultSystemUsers() *SystemUser {
return &SystemUser{
ControllerManager: constant.ControllerManagerUser,
Etcd: constant.EtcdUser,
Kine: constant.KineUser,
Konnectivity: constant.KonnectivityServerUser,
KubeAPIServer: constant.ApiserverUser,
KubeScheduler: constant.SchedulerUser,
}
}

// DefaultInstallSpec ...
func DefaultInstallSpec() *InstallSpec {
return &InstallSpec{
SystemUsers: DefaultSystemUsers(),
}
}
87 changes: 87 additions & 0 deletions pkg/install/users.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package install

import (
"fmt"
"os"
"os/exec"
"os/user"
"strings"

"github.com/sirupsen/logrus"

"github.com/k0sproject/k0s/internal/util"
)

// EnsureUser checks if a user exists, and creates it, if it doesn't
// TODO: we should also consider modifying the user, if the user exists, but with wrong settings
func EnsureUser(name string, homeDir string) error {
jasmingacic marked this conversation as resolved.
Show resolved Hide resolved
shell, err := util.GetExecPath("nologin")
if err != nil {
return err
}

exists, err := util.CheckIfUserExists(name)
// User doesn't exist
if !exists && err == nil {
// Create the User
if err := CreateUser(name, homeDir, *shell); err != nil {
return err
}
// User perhaps exists, but cannot be fetched
} else if err != nil {
return err
}
// verify that user can be fetched, and exists
_, err = user.Lookup(name)
if err != nil {
return err
}
return nil
}

// CreateUser creates a system user with either `adduser` or `useradd` command
func CreateUser(userName string, homeDir string, shell string) error {
var userCmd string
var userCmdArgs []string

logrus.Infof("creating user: %s", userName)
_, err := util.GetExecPath("useradd")
if err == nil {
userCmd = "useradd"
userCmdArgs = []string{`--home`, homeDir, `--shell`, shell, `--system`, `--no-create-home`, userName}
} else {
userCmd = "adduser"
userCmdArgs = []string{`--disabled-password`, `--gecos`, `""`, `--home`, homeDir, `--shell`, shell, `--system`, `--no-create-home`, userName}
}

cmd := exec.Command(userCmd, userCmdArgs...)
if err := execCmd(cmd); err != nil {
return err
}
return nil
}

// cmd wrapper
func execCmd(cmd *exec.Cmd) error {
logrus.Debugf("executing command: %v", quoteCmd(cmd))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to run command %s: %v", quoteCmd(cmd), err)
}
return nil
}

// parse a cmd struct to string
func quoteCmd(cmd *exec.Cmd) string {
if len(cmd.Args) == 0 {
return fmt.Sprintf("%q", cmd.Path)
}

var q []string
for _, s := range cmd.Args {
q = append(q, fmt.Sprintf("%q", s))
}
return strings.Join(q, ` `)
}