mirror of https://github.com/kubernetes/kops.git
Merge pull request #15 from justinsb/master_pd
upup: Support for persistent disks, users & symlinks
This commit is contained in:
commit
3703ecedc8
|
@ -2,7 +2,8 @@ gocode: godeps
|
|||
go install k8s.io/kube-deploy/upup/cmd/...
|
||||
|
||||
godeps:
|
||||
glide install
|
||||
# I think strip-vendor is the workaround for 25572
|
||||
glide install --strip-vendor --strip-vcs
|
||||
|
||||
tar: gocode
|
||||
rm -rf .build/tar
|
||||
|
@ -26,13 +27,14 @@ push: tar
|
|||
scp .build/nodeup.tar.gz ${TARGET}:/tmp/
|
||||
ssh ${TARGET} sudo tar zxf /tmp/nodeup.tar.gz -C /var/cache/kubernetes-install
|
||||
|
||||
push-dry: push
|
||||
# GCE:
|
||||
# ssh ${TARGET} sudo SKIP_PACKAGE_UPDATE=1 /var/cache/kubernetes-install/nodeup/root/nodeup --conf=metadata://gce/config --dryrun --v=8 --model=/var/cache/kubernetes-install/nodeup/root/model
|
||||
push-gce-dry: push
|
||||
ssh ${TARGET} sudo SKIP_PACKAGE_UPDATE=1 /var/cache/kubernetes-install/nodeup/root/nodeup --conf=metadata://gce/config --dryrun --v=8 --model=/var/cache/kubernetes-install/nodeup/root/model
|
||||
|
||||
push-aws-dry: push
|
||||
ssh ${TARGET} sudo SKIP_PACKAGE_UPDATE=1 /var/cache/kubernetes-install/nodeup/root/nodeup --conf=/var/cache/kubernetes-install/kube_env.yaml --dryrun --v=8 --model=/var/cache/kubernetes-install/nodeup/root/model
|
||||
|
||||
push-run: push
|
||||
# GCE:
|
||||
# #ssh ${TARGET} sudo SKIP_PACKAGE_UPDATE=1 /var/cache/kubernetes-install/nodeup/root/nodeup --conf=metadata://gce/config --v=8 --model=/var/cache/kubernetes-install/nodeup/root/model
|
||||
# AWS:
|
||||
push-gce-run: push
|
||||
ssh ${TARGET} sudo SKIP_PACKAGE_UPDATE=1 /var/cache/kubernetes-install/nodeup/root/nodeup --conf=metadata://gce/config --v=8 --model=/var/cache/kubernetes-install/nodeup/root/model
|
||||
|
||||
push-aws-run: push
|
||||
ssh ${TARGET} sudo SKIP_PACKAGE_UPDATE=1 /var/cache/kubernetes-install/nodeup/root/nodeup --conf=/var/cache/kubernetes-install/kube_env.yaml --v=8 --model=/var/cache/kubernetes-install/nodeup/root/model
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
hash: 9864d4fe0b94042cab08d2e80910638776d3d1e655b5a3cb9006731ae0306761
|
||||
updated: 2016-05-09T11:48:29.232191357-04:00
|
||||
updated: 2016-05-13T09:50:25.860201305-04:00
|
||||
imports:
|
||||
- name: github.com/aws/aws-sdk-go
|
||||
version: d85fa529a99a833067e11c0a838b9db7a5d5ea71
|
||||
|
@ -87,4 +87,9 @@ imports:
|
|||
- internal
|
||||
- name: google.golang.org/grpc
|
||||
version: 9604a2bb7dd81d87c2873a9580258465f3c311c8
|
||||
- name: k8s.io/kubernetes
|
||||
version: bb3f5b1768f3bf6c81914c5bb1d7e846561fdc31
|
||||
subpackages:
|
||||
- pkg/util/exec
|
||||
- pkg/util/mount
|
||||
devImports: []
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# TODO: Should we set a consistent UID in case we remount?
|
||||
|
||||
shell: /sbin/nologin
|
||||
home: /var/etcd
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
device: /dev/xvdb
|
||||
mountpoint: /mnt/master-pd
|
|
@ -0,0 +1,2 @@
|
|||
device: /dev/disk/by-id/google-master-pd
|
||||
mountpoint: /mnt/master-pd
|
|
@ -0,0 +1 @@
|
|||
directory: true
|
|
@ -0,0 +1 @@
|
|||
directory: true
|
|
@ -0,0 +1,4 @@
|
|||
owner: etcd
|
||||
group: etcd
|
||||
directory: true
|
||||
mode: "0700"
|
|
@ -0,0 +1 @@
|
|||
symlink: /mnt/master-pd/srv/kubernetes
|
|
@ -0,0 +1 @@
|
|||
symlink: /mnt/master-pd/srv/sshproxy
|
|
@ -0,0 +1 @@
|
|||
symlink: /mnt/master-pd/var/etcd
|
|
@ -44,9 +44,9 @@ func (e *InstanceVolumeAttachment) Find(c *fi.Context) (*InstanceVolumeAttachmen
|
|||
}
|
||||
|
||||
actual := &InstanceVolumeAttachment{
|
||||
Device: bdm.DeviceName,
|
||||
Device: bdm.DeviceName,
|
||||
Instance: &Instance{ID: instance.InstanceId},
|
||||
Volume: &EBSVolume{ID: bdm.Ebs.VolumeId},
|
||||
Volume: &EBSVolume{ID: bdm.Ebs.VolumeId},
|
||||
}
|
||||
|
||||
glog.V(2).Infof("found matching InstanceVolumeAttachmen")
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func WriteFile(destPath string, contents Resource, fileMode os.FileMode, dirMode os.FileMode) error {
|
||||
|
@ -52,11 +53,11 @@ func writeFileContents(destPath string, src Resource, fileMode os.FileMode) erro
|
|||
|
||||
func EnsureFileMode(destPath string, fileMode os.FileMode) (bool, error) {
|
||||
changed := false
|
||||
stat, err := os.Stat(destPath)
|
||||
stat, err := os.Lstat(destPath)
|
||||
if err != nil {
|
||||
return changed, fmt.Errorf("error getting file mode for %q: %v", destPath, err)
|
||||
}
|
||||
if stat.Mode() == fileMode {
|
||||
if (stat.Mode() & os.ModePerm) == fileMode {
|
||||
return changed, nil
|
||||
}
|
||||
glog.Infof("Changing file mode for %q to %s", destPath, fileMode)
|
||||
|
@ -69,6 +70,37 @@ func EnsureFileMode(destPath string, fileMode os.FileMode) (bool, error) {
|
|||
return changed, nil
|
||||
}
|
||||
|
||||
func EnsureFileOwner(destPath string, owner string, groupName string) (bool, error) {
|
||||
changed := false
|
||||
stat, err := os.Lstat(destPath)
|
||||
if err != nil {
|
||||
return changed, fmt.Errorf("error getting file stat for %q: %v", destPath, err)
|
||||
}
|
||||
|
||||
user, err := LookupUser(owner) //user.Lookup(owner)
|
||||
if err != nil {
|
||||
return changed, fmt.Errorf("error looking up user %q: %v", owner, err)
|
||||
}
|
||||
|
||||
group, err := LookupGroup(groupName)
|
||||
if err != nil {
|
||||
return changed, fmt.Errorf("error looking up group %q: %v", groupName, err)
|
||||
}
|
||||
|
||||
if int(stat.Sys().(*syscall.Stat_t).Uid) == user.Uid && int(stat.Sys().(*syscall.Stat_t).Gid) == group.Gid {
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
glog.Infof("Changing file owner/group for %q to %s:%s", destPath, owner, group)
|
||||
err = os.Lchown(destPath, user.Uid, group.Gid)
|
||||
if err != nil {
|
||||
return changed, fmt.Errorf("error setting file owner/group for %q: %v", destPath, err)
|
||||
}
|
||||
changed = true
|
||||
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
func fileHasHash(f string, expected string) (bool, error) {
|
||||
hashAlgorithm, err := determineHashAlgorithm(expected)
|
||||
if err != nil {
|
||||
|
|
|
@ -79,6 +79,22 @@ func (t *TreeWalker) walkDirectory(parent *TreeWalkItem) error {
|
|||
|
||||
glog.V(4).Infof("visit %q", i.Path)
|
||||
|
||||
hasMeta := false
|
||||
{
|
||||
metaPath := i.Path + ".meta"
|
||||
metaBytes, err := ioutil.ReadFile(metaPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("error reading file %q: %v", metaPath, err)
|
||||
}
|
||||
metaBytes = nil
|
||||
}
|
||||
if metaBytes != nil {
|
||||
hasMeta = true
|
||||
i.Meta = string(metaBytes)
|
||||
}
|
||||
}
|
||||
|
||||
if f.IsDir() {
|
||||
if IsTag(fileName) {
|
||||
// Only descend into the tag directory if we have the tag
|
||||
|
@ -106,7 +122,13 @@ func (t *TreeWalker) walkDirectory(parent *TreeWalkItem) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
|
||||
// So that we can manage directories, we do not ignore directories which have a .meta file
|
||||
if hasMeta {
|
||||
glog.V(4).Infof("Found .meta file for directory %q; will process", i.Path)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasSuffix(fileName, ".meta") {
|
||||
|
@ -120,24 +142,12 @@ func (t *TreeWalker) walkDirectory(parent *TreeWalkItem) error {
|
|||
continue
|
||||
}
|
||||
|
||||
{
|
||||
metaPath := i.Path + ".meta"
|
||||
metaBytes, err := ioutil.ReadFile(metaPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("error reading file %q: %v", metaPath, err)
|
||||
}
|
||||
metaBytes = nil
|
||||
}
|
||||
if metaBytes != nil {
|
||||
i.Meta = string(metaBytes)
|
||||
}
|
||||
}
|
||||
|
||||
var handler Handler
|
||||
if i.Context != "" {
|
||||
handler = t.Contexts[i.Context]
|
||||
} else {
|
||||
// TODO: Just remove extensions.... we barely use them!
|
||||
// (or remove default handler and replace with lots of small files?)
|
||||
extension := path.Ext(fileName)
|
||||
handler = t.Extensions[extension]
|
||||
if handler == nil {
|
||||
|
|
|
@ -122,6 +122,10 @@ func (t *CloudInitTarget) WriteFile(destPath string, contents fi.Resource, fileM
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *CloudInitTarget) Chown(path string, user, group string) {
|
||||
t.AddCommand(Always, "chown", user+":"+group, path)
|
||||
}
|
||||
|
||||
func stringSlicesEquals(l, r []string) bool {
|
||||
if len(l) != len(r) {
|
||||
return false
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"k8s.io/kube-deploy/upup/pkg/fi"
|
||||
"k8s.io/kube-deploy/upup/pkg/fi/loader"
|
||||
"k8s.io/kube-deploy/upup/pkg/fi/nodeup/nodetasks"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
@ -75,9 +76,11 @@ func (l *Loader) Build(baseDir string) (map[string]fi.Task, error) {
|
|||
DefaultHandler: ignoreHandler,
|
||||
Contexts: map[string]loader.Handler{
|
||||
"options": l.optionsLoader.HandleOptions,
|
||||
"files": ignoreHandler,
|
||||
"disks": ignoreHandler,
|
||||
"packages": ignoreHandler,
|
||||
"services": ignoreHandler,
|
||||
"files": ignoreHandler,
|
||||
"users": ignoreHandler,
|
||||
},
|
||||
Tags: tags,
|
||||
}
|
||||
|
@ -99,9 +102,11 @@ func (l *Loader) Build(baseDir string) (map[string]fi.Task, error) {
|
|||
DefaultHandler: l.handleFile,
|
||||
Contexts: map[string]loader.Handler{
|
||||
"options": ignoreHandler,
|
||||
"files": l.handleFile,
|
||||
"disks": l.newTaskHandler("disk/", nodetasks.NewMountDiskTask),
|
||||
"packages": l.newTaskHandler("package/", nodetasks.NewPackage),
|
||||
"services": l.newTaskHandler("service/", nodetasks.NewService),
|
||||
"files": l.handleFile,
|
||||
"users": l.newTaskHandler("user/", nodetasks.NewUserTask),
|
||||
},
|
||||
Tags: tags,
|
||||
}
|
||||
|
@ -143,7 +148,9 @@ func (r *Loader) newTaskHandler(prefix string, builder TaskBuilder) loader.Handl
|
|||
}
|
||||
|
||||
func (r *Loader) handleFile(i *loader.TreeWalkItem) error {
|
||||
var task fi.Task
|
||||
var task *nodetasks.File
|
||||
defaultFileType := nodetasks.FileType_File
|
||||
|
||||
var err error
|
||||
if strings.HasSuffix(i.RelativePath, ".template") {
|
||||
contents, err := i.ReadString()
|
||||
|
@ -185,7 +192,21 @@ func (r *Loader) handleFile(i *loader.TreeWalkItem) error {
|
|||
|
||||
task, err = nodetasks.NewFileTask(i.Name, asset, destPath, i.Meta)
|
||||
} else {
|
||||
task, err = nodetasks.NewFileTask(i.Name, fi.NewFileResource(i.Path), "/"+i.RelativePath, i.Meta)
|
||||
stat, err := os.Stat(i.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error doing stat on %q: %v", i.Path, err)
|
||||
}
|
||||
var contents fi.Resource
|
||||
if stat.IsDir() {
|
||||
defaultFileType = nodetasks.FileType_Directory
|
||||
} else {
|
||||
contents = fi.NewFileResource(i.Path)
|
||||
}
|
||||
task, err = nodetasks.NewFileTask(i.Name, contents, "/"+i.RelativePath, i.Meta)
|
||||
}
|
||||
|
||||
if task.Type == "" {
|
||||
task.Type = defaultFileType
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
package nodetasks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kube-deploy/upup/pkg/fi"
|
||||
"k8s.io/kube-deploy/upup/pkg/fi/nodeup/cloudinit"
|
||||
"k8s.io/kube-deploy/upup/pkg/fi/nodeup/local"
|
||||
"k8s.io/kube-deploy/upup/pkg/fi/utils"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const FileType_Symlink = "symlink"
|
||||
const FileType_Directory = "directory"
|
||||
const FileType_File = "file"
|
||||
|
||||
type File struct {
|
||||
Path string
|
||||
Contents fi.Resource
|
||||
|
@ -20,9 +27,15 @@ type File struct {
|
|||
IfNotExists bool `json:"ifNotExists"`
|
||||
|
||||
OnChangeExecute []string `json:"onChangeExecute,omitempty"`
|
||||
|
||||
Symlink *string `json:"symlink,omitempty"`
|
||||
Owner *string `json:"owner,omitempty"`
|
||||
Group *string `json:"group,omitempty"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
var _ fi.Task = &File{}
|
||||
var _ fi.HasDependencies = &File{}
|
||||
|
||||
func NewFileTask(name string, src fi.Resource, destPath string, meta string) (*File, error) {
|
||||
f := &File{
|
||||
|
@ -32,21 +45,43 @@ func NewFileTask(name string, src fi.Resource, destPath string, meta string) (*F
|
|||
}
|
||||
|
||||
if meta != "" {
|
||||
err := json.Unmarshal([]byte(meta), f)
|
||||
err := utils.YamlUnmarshal([]byte(meta), f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing meta for file %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if f.Symlink != nil && f.Type == "" {
|
||||
f.Type = FileType_Symlink
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (f *File) GetDependencies(tasks map[string]fi.Task) []string {
|
||||
var deps []string
|
||||
if f.Owner != nil {
|
||||
deps = append(deps, "user/"+*f.Owner)
|
||||
}
|
||||
|
||||
// Depend on disk mounts
|
||||
// For simplicity, we just depend on _all_ disk mounts
|
||||
// We could check the mountpath, but that feels excessive...
|
||||
for k, v := range tasks {
|
||||
if _, ok := v.(*MountDiskTask); ok {
|
||||
deps = append(deps, k)
|
||||
}
|
||||
}
|
||||
|
||||
return deps
|
||||
}
|
||||
|
||||
func (f *File) String() string {
|
||||
return fmt.Sprintf("File: %q", f.Path)
|
||||
}
|
||||
|
||||
func findFile(p string) (*File, error) {
|
||||
stat, err := os.Stat(p)
|
||||
stat, err := os.Lstat(p)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
|
@ -55,8 +90,44 @@ func findFile(p string) (*File, error) {
|
|||
|
||||
actual := &File{}
|
||||
actual.Path = p
|
||||
actual.Mode = fi.String(fi.FileModeToString(stat.Mode()))
|
||||
actual.Contents = fi.NewFileResource(p)
|
||||
actual.Mode = fi.String(fi.FileModeToString(stat.Mode() & os.ModePerm))
|
||||
|
||||
uid := int(stat.Sys().(*syscall.Stat_t).Uid)
|
||||
owner, err := fi.LookupUserById(uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if owner != nil {
|
||||
actual.Owner = fi.String(owner.Name)
|
||||
} else {
|
||||
actual.Owner = fi.String(strconv.Itoa(uid))
|
||||
}
|
||||
|
||||
gid := int(stat.Sys().(*syscall.Stat_t).Gid)
|
||||
group, err := fi.LookupGroupById(gid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if group != nil {
|
||||
actual.Group = fi.String(group.Name)
|
||||
} else {
|
||||
actual.Group = fi.String(strconv.Itoa(gid))
|
||||
}
|
||||
|
||||
if (stat.Mode() & os.ModeSymlink) != 0 {
|
||||
target, err := os.Readlink(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading symlink target: %v", err)
|
||||
}
|
||||
|
||||
actual.Type = FileType_Symlink
|
||||
actual.Symlink = fi.String(target)
|
||||
} else if (stat.Mode() & os.ModeDir) != 0 {
|
||||
actual.Type = FileType_Directory
|
||||
} else {
|
||||
actual.Type = FileType_File
|
||||
actual.Contents = fi.NewFileResource(p)
|
||||
}
|
||||
|
||||
return actual, nil
|
||||
}
|
||||
|
@ -72,6 +143,10 @@ func (e *File) Find(c *fi.Context) (*File, error) {
|
|||
|
||||
// To avoid spurious changes
|
||||
actual.IfNotExists = e.IfNotExists
|
||||
if e.IfNotExists {
|
||||
actual.Contents = e.Contents
|
||||
}
|
||||
actual.OnChangeExecute = e.OnChangeExecute
|
||||
|
||||
return actual, nil
|
||||
}
|
||||
|
@ -88,7 +163,7 @@ func (_ *File) RenderLocal(t *local.LocalTarget, a, e, changes *File) error {
|
|||
dirMode := os.FileMode(0755)
|
||||
fileMode, err := fi.ParseFileMode(fi.StringValue(e.Mode), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid file mode for %q: %q", e.Path, e.Mode)
|
||||
return fmt.Errorf("invalid file mode for %q: %q", e.Path, fi.StringValue(e.Mode))
|
||||
}
|
||||
|
||||
if a != nil {
|
||||
|
@ -99,20 +174,59 @@ func (_ *File) RenderLocal(t *local.LocalTarget, a, e, changes *File) error {
|
|||
}
|
||||
|
||||
changed := false
|
||||
if changes.Contents != nil {
|
||||
err = fi.WriteFile(e.Path, e.Contents, fileMode, dirMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error copying file %q: %v", e.Path, err)
|
||||
if e.Type == FileType_Symlink {
|
||||
if changes.Symlink != nil {
|
||||
// This will currently fail if the target already exists.
|
||||
// That's probably a good thing for now ... it is hard to know what to do here!
|
||||
glog.Infof("Creating symlink %q -> %q", e.Path, *changes.Symlink)
|
||||
err := os.Symlink(*changes.Symlink, e.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating symlink %q -> %q: %v", e.Path, *changes.Symlink, err)
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
changed = true
|
||||
} else if changes.Mode != nil {
|
||||
} else if e.Type == FileType_Directory {
|
||||
if a == nil {
|
||||
parent := filepath.Dir(strings.TrimSuffix(e.Path, "/"))
|
||||
err := os.MkdirAll(parent, dirMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating parent directories %q: %v", parent, err)
|
||||
}
|
||||
|
||||
err = os.MkdirAll(e.Path, fileMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating directory %q: %v", e.Path, err)
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
} else if e.Type == FileType_File {
|
||||
if changes.Contents != nil {
|
||||
err = fi.WriteFile(e.Path, e.Contents, fileMode, dirMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error copying file %q: %v", e.Path, err)
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("File type=%q not valid/supported", e.Type)
|
||||
}
|
||||
|
||||
if changes.Mode != nil {
|
||||
modeChanged, err := fi.EnsureFileMode(e.Path, fileMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error changing file mode %q: %v", e.Path, err)
|
||||
return fmt.Errorf("error changing mode on %q: %v", e.Path, err)
|
||||
}
|
||||
changed = changed || modeChanged
|
||||
}
|
||||
|
||||
if changes.Owner != nil || changes.Group != nil {
|
||||
ownerChanged, err := fi.EnsureFileOwner(e.Path, fi.StringValue(e.Owner), fi.StringValue(e.Group))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error changing owner/group on %q: %v", e.Path, err)
|
||||
}
|
||||
changed = changed || ownerChanged
|
||||
}
|
||||
|
||||
if changed && e.OnChangeExecute != nil {
|
||||
args := e.OnChangeExecute
|
||||
human := strings.Join(args, " ")
|
||||
|
@ -136,9 +250,23 @@ func (_ *File) RenderCloudInit(t *cloudinit.CloudInitTarget, a, e, changes *File
|
|||
return fmt.Errorf("invalid file mode for %q: %q", e.Path, e.Mode)
|
||||
}
|
||||
|
||||
err = t.WriteFile(e.Path, e.Contents, fileMode, dirMode)
|
||||
if err != nil {
|
||||
return err
|
||||
if e.Type == FileType_Symlink {
|
||||
t.AddCommand(cloudinit.Always, "ln", "-s", fi.StringValue(e.Symlink), e.Path)
|
||||
} else if e.Type == FileType_Directory {
|
||||
parent := filepath.Dir(strings.TrimSuffix(e.Path, "/"))
|
||||
t.AddCommand(cloudinit.Once, "mkdir", "-p", "-m", fi.FileModeToString(dirMode), parent)
|
||||
t.AddCommand(cloudinit.Once, "mkdir", "-m", fi.FileModeToString(dirMode), e.Path)
|
||||
} else if e.Type == FileType_File {
|
||||
err = t.WriteFile(e.Path, e.Contents, fileMode, dirMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("File type=%q not valid/supported", e.Type)
|
||||
}
|
||||
|
||||
if e.Owner != nil || e.Group != nil {
|
||||
t.Chown(e.Path, fi.StringValue(e.Owner), fi.StringValue(e.Group))
|
||||
}
|
||||
|
||||
if e.OnChangeExecute != nil {
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
package nodetasks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kube-deploy/upup/pkg/fi"
|
||||
"k8s.io/kube-deploy/upup/pkg/fi/nodeup/cloudinit"
|
||||
"k8s.io/kube-deploy/upup/pkg/fi/nodeup/local"
|
||||
"k8s.io/kube-deploy/upup/pkg/fi/utils"
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MountDiskTask is responsible for mounting a device on a mountpoint
|
||||
// It will wait for the device to show up, safe_format_and_mount it,
|
||||
// and then mount it.
|
||||
type MountDiskTask struct {
|
||||
Name string
|
||||
|
||||
Device string `json:"device"`
|
||||
Mountpoint string `json:"mountpoint"`
|
||||
}
|
||||
|
||||
var _ fi.Task = &MountDiskTask{}
|
||||
|
||||
func (s *MountDiskTask) String() string {
|
||||
return fmt.Sprintf("Disk: %s", s.Name)
|
||||
}
|
||||
|
||||
func NewMountDiskTask(name string, contents string, meta string) (fi.Task, error) {
|
||||
s := &MountDiskTask{Name: name}
|
||||
|
||||
err := utils.YamlUnmarshal([]byte(contents), s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing json for disk %q: %v", name, err)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (e *MountDiskTask) Find(c *fi.Context) (*MountDiskTask, error) {
|
||||
mounter := mount.New()
|
||||
|
||||
mps, err := mounter.List()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error finding existing mounts: %v", err)
|
||||
}
|
||||
|
||||
// If device is a symlink, it will show up by its final name
|
||||
targetDevice, err := filepath.EvalSymlinks(e.Device)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error resolving device symlinks for %q: %v", e.Device, err)
|
||||
}
|
||||
|
||||
for i := range mps {
|
||||
mp := &mps[i]
|
||||
if mp.Device == targetDevice {
|
||||
actual := &MountDiskTask{
|
||||
Name: e.Name,
|
||||
Mountpoint: mp.Path,
|
||||
Device: e.Device, // Use our alias, to keep change detection happy
|
||||
}
|
||||
return actual, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e *MountDiskTask) Run(c *fi.Context) error {
|
||||
return fi.DefaultDeltaRunMethod(e, c)
|
||||
}
|
||||
|
||||
func (s *MountDiskTask) CheckChanges(a, e, changes *MountDiskTask) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_ *MountDiskTask) RenderLocal(t *local.LocalTarget, a, e, changes *MountDiskTask) error {
|
||||
dirMode := os.FileMode(0755)
|
||||
|
||||
// Create the mountpoint
|
||||
err := os.MkdirAll(e.Mountpoint, dirMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating mountpoint %q: %v", e.Mountpoint, err)
|
||||
}
|
||||
|
||||
// Wait for the device to show up
|
||||
for {
|
||||
_, err := os.Stat(e.Device)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("error checking for device %q: %v", e.Device, err)
|
||||
}
|
||||
glog.Infof("Waiting for device %q to be attached", e.Device)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
glog.Infof("Found device %q", e.Device)
|
||||
|
||||
// Mount the device
|
||||
if changes.Mountpoint != "" {
|
||||
glog.Infof("Mounting device %q on %q", e.Device, e.Mountpoint)
|
||||
|
||||
mounter := &mount.SafeFormatAndMount{Interface: mount.New(), Runner: exec.New()}
|
||||
|
||||
fstype := ""
|
||||
options := []string{}
|
||||
|
||||
err := mounter.FormatAndMount(e.Device, e.Mountpoint, fstype, options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error formatting and mounting disk %q on %q: %v", e.Device, e.Mountpoint, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Should we add to /etc/fstab?
|
||||
// Mount the master PD as early as possible
|
||||
// echo "/dev/xvdb /mnt/master-pd ext4 noatime 0 0" >> /etc/fstab
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_ *MountDiskTask) RenderCloudInit(t *cloudinit.CloudInitTarget, a, e, changes *MountDiskTask) error {
|
||||
// TODO: Run safe_format_and_mount
|
||||
// Download on aws (or bake into image)
|
||||
// # TODO: Where to get safe_format_and_mount?
|
||||
//mkdir -p /usr/share/google
|
||||
//cd /usr/share/google
|
||||
//download-or-bust "dc96f40fdc9a0815f099a51738587ef5a976f1da" https://raw.githubusercontent.com/GoogleCloudPlatform/compute-image-packages/82b75f314528b90485d5239ab5d5495cc22d775f/google-startup-scripts/usr/share/google/safe_format_and_mount
|
||||
//chmod +x safe_format_and_mount
|
||||
|
||||
return fmt.Errorf("Disk::RenderCloudInit not implemented")
|
||||
}
|
|
@ -36,12 +36,13 @@ func (p *Service) GetDependencies(tasks map[string]fi.Task) []string {
|
|||
// We assume that services depend on basically everything
|
||||
typeName := utils.BuildTypeName(reflect.TypeOf(v))
|
||||
switch typeName {
|
||||
case "*CopyAssetTask", "*File", "*Package", "*Sysctl", "*UpdatePackages":
|
||||
case "*CopyAssetTask", "*File", "*Package", "*Sysctl", "*UpdatePackages", "*User", "*Disk":
|
||||
deps = append(deps, k)
|
||||
case "*Service":
|
||||
// ignore
|
||||
default:
|
||||
glog.Fatalf("Unhandled type name: %q", typeName)
|
||||
glog.Warningf("Unhandled type name in Service::GetDependencies: %q", typeName)
|
||||
deps = append(deps, k)
|
||||
}
|
||||
}
|
||||
return deps
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
package nodetasks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kube-deploy/upup/pkg/fi"
|
||||
"k8s.io/kube-deploy/upup/pkg/fi/nodeup/cloudinit"
|
||||
"k8s.io/kube-deploy/upup/pkg/fi/nodeup/local"
|
||||
"k8s.io/kube-deploy/upup/pkg/fi/utils"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// UserTask is responsible for creating a user, by calling useradd
|
||||
type UserTask struct {
|
||||
Name string
|
||||
|
||||
Shell string `json:"shell"`
|
||||
Home string `json:"home"`
|
||||
}
|
||||
|
||||
var _ fi.Task = &UserTask{}
|
||||
|
||||
func (e *UserTask) String() string {
|
||||
return fmt.Sprintf("User: %s", e.Name)
|
||||
}
|
||||
|
||||
func NewUserTask(name string, contents string, meta string) (fi.Task, error) {
|
||||
s := &UserTask{Name: name}
|
||||
|
||||
err := utils.YamlUnmarshal([]byte(contents), s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing json for service %q: %v", name, err)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (e *UserTask) Find(c *fi.Context) (*UserTask, error) {
|
||||
info, err := fi.LookupUser(e.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
actual := &UserTask{
|
||||
Name: e.Name,
|
||||
Shell: info.Shell,
|
||||
Home: info.Home,
|
||||
}
|
||||
|
||||
return actual, nil
|
||||
}
|
||||
|
||||
func (e *UserTask) Run(c *fi.Context) error {
|
||||
return fi.DefaultDeltaRunMethod(e, c)
|
||||
}
|
||||
|
||||
func (_ *UserTask) CheckChanges(a, e, changes *UserTask) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildUseraddArgs(e *UserTask) []string {
|
||||
var args []string
|
||||
if e.Shell != "" {
|
||||
args = append(args, "-s", e.Shell)
|
||||
}
|
||||
if e.Home != "" {
|
||||
args = append(args, "-d", e.Home)
|
||||
}
|
||||
args = append(args, e.Name)
|
||||
return args
|
||||
}
|
||||
|
||||
func (_ *UserTask) RenderLocal(t *local.LocalTarget, a, e, changes *UserTask) error {
|
||||
if a == nil {
|
||||
args := buildUseraddArgs(e)
|
||||
glog.Infof("Creating user %q", e.Name)
|
||||
cmd := exec.Command("useradd", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating user: %v\nOutput: %s", err, output)
|
||||
}
|
||||
} else {
|
||||
var args []string
|
||||
|
||||
if changes.Shell != "" {
|
||||
args = append(args, "-s", e.Shell)
|
||||
}
|
||||
if changes.Home != "" {
|
||||
args = append(args, "-d", e.Home)
|
||||
}
|
||||
|
||||
if len(args) != 0 {
|
||||
args = append(args, e.Name)
|
||||
glog.Infof("Reconfiguring user %q", e.Name)
|
||||
cmd := exec.Command("usermod", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reconfiguring user: %v\nOutput: %s", err, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_ *UserTask) RenderCloudInit(t *cloudinit.CloudInitTarget, a, e, changes *UserTask) error {
|
||||
args := buildUseraddArgs(e)
|
||||
cmd := []string{"useradd"}
|
||||
cmd = append(cmd, args...)
|
||||
glog.Infof("Creating user %q", e.Name)
|
||||
t.AddCommand(cloudinit.Once, cmd...)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -32,11 +32,18 @@ func TopologicalSort(tasks map[string]Task) [][]string {
|
|||
}
|
||||
|
||||
edges[k] = dependencyKeys
|
||||
glog.V(4).Infof("%s : %v", k, dependencyKeys)
|
||||
}
|
||||
|
||||
glog.V(4).Infof("Dependencies:")
|
||||
for k, v := range edges {
|
||||
glog.V(4).Infof("\t%s:\t%v", k, v)
|
||||
}
|
||||
|
||||
ordered := toposort(edges)
|
||||
glog.V(1).Infof("toposorted as %v", ordered)
|
||||
glog.V(1).Infof("toposorted as:")
|
||||
for i, stage := range ordered {
|
||||
glog.V(1).Infof("\t%d\t%v", i, stage)
|
||||
}
|
||||
|
||||
return ordered
|
||||
}
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
package fi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// This file parses /etc/passwd and /etc/group to get information about users & groups
|
||||
// Go has built-in user functionality, and group functionality is merged but not yet released
|
||||
// TODO: Replace this file with e.g. user.LookupGroup once 42f07ff2679d38a03522db3ccd488f4cc230c8c2 lands in go 1.7
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
Uid int
|
||||
Gid int
|
||||
Comment string
|
||||
Home string
|
||||
Shell string
|
||||
}
|
||||
|
||||
func parseUsers() (map[string]*User, error) {
|
||||
users := make(map[string]*User)
|
||||
|
||||
path := "/etc/passwd"
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading user file %q", path)
|
||||
}
|
||||
for _, line := range strings.Split(string(b), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
tokens := strings.Split(line, ":")
|
||||
|
||||
if len(tokens) < 7 {
|
||||
glog.Warning("Ignoring malformed /etc/passwd line (too few tokens): %q", line)
|
||||
continue
|
||||
}
|
||||
|
||||
uid, err := strconv.Atoi(tokens[2])
|
||||
if err != nil {
|
||||
glog.Warning("Ignoring malformed /etc/passwd line (bad uid): %q", line)
|
||||
continue
|
||||
}
|
||||
gid, err := strconv.Atoi(tokens[3])
|
||||
if err != nil {
|
||||
glog.Warning("Ignoring malformed /etc/passwd line (bad gid): %q", line)
|
||||
continue
|
||||
}
|
||||
|
||||
u := &User{
|
||||
Name: tokens[0],
|
||||
// Password
|
||||
Uid: uid,
|
||||
Gid: gid,
|
||||
Comment: tokens[4],
|
||||
Home: tokens[5],
|
||||
Shell: tokens[6],
|
||||
}
|
||||
users[u.Name] = u
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func LookupUser(name string) (*User, error) {
|
||||
users, err := parseUsers()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading users: %v", err)
|
||||
}
|
||||
return users[name], nil
|
||||
}
|
||||
|
||||
func LookupUserById(uid int) (*User, error) {
|
||||
users, err := parseUsers()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading users: %v", err)
|
||||
}
|
||||
for _, v := range users {
|
||||
if v.Uid == uid {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
Name string
|
||||
Gid int
|
||||
//Members []string
|
||||
}
|
||||
|
||||
func parseGroups() (map[string]*Group, error) {
|
||||
groups := make(map[string]*Group)
|
||||
|
||||
path := "/etc/group"
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading group file %q", path)
|
||||
}
|
||||
for _, line := range strings.Split(string(b), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
tokens := strings.Split(line, ":")
|
||||
|
||||
if len(tokens) < 4 {
|
||||
glog.Warning("Ignoring malformed /etc/group line (too few tokens): %q", line)
|
||||
continue
|
||||
}
|
||||
|
||||
gid, err := strconv.Atoi(tokens[2])
|
||||
if err != nil {
|
||||
glog.Warning("Ignoring malformed /etc/group line (bad gid): %q", line)
|
||||
continue
|
||||
}
|
||||
|
||||
g := &Group{
|
||||
Name: tokens[0],
|
||||
// Password: tokens[1]
|
||||
Gid: gid,
|
||||
// Members: strings.Split(tokens[3], ",")
|
||||
}
|
||||
groups[g.Name] = g
|
||||
}
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func LookupGroup(name string) (*Group, error) {
|
||||
groups, err := parseGroups()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading groups: %v", err)
|
||||
}
|
||||
return groups[name], nil
|
||||
}
|
||||
|
||||
func LookupGroupById(gid int) (*Group, error) {
|
||||
users, err := parseGroups()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading groups: %v", err)
|
||||
}
|
||||
for _, v := range users {
|
||||
if v.Gid == gid {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
Loading…
Reference in New Issue